arckode-framework
v1.1.2
Published
AI-first TypeScript/Bun framework. Modular, SOLID, zero magic. The AI reads the composition root and knows everything.
Maintainers
Readme
Arckode Framework
Framework TypeScript/Bun para construir APIs modulares. Diseñado desde el principio para trabajar con IA: arquitectura predecible, límites claros, cero magia.
arckode new mi-api
cd mi-api && bun install
arckode make:auth
arckode make:module Productos
bun run devPor qué Arckode
La mayoría de los frameworks están optimizados para que los humanos escriban código. Arckode está optimizado para que la IA genere código correcto sin supervisión constante.
La IA lee el composition-root.ts y sabe todo lo que necesita:
- Qué módulos existen y qué hacen
- Cómo se conectan entre sí
- Qué datos maneja cada uno
- Qué reglas tienen que cumplir
No hay decoradores, no hay magia de inyección, no hay convenciones implícitas. Todo está explícito en un solo lugar.
Instalación
Requisito: Bun >= 1.0
# 1. Instalar el CLI globalmente — una sola vez por máquina
bun install -g arckode-framework
# 2. Crear tu proyecto
arckode new mi-api
# 3. Entrar al proyecto e instalar dependencias
cd mi-api && bun install
# 4. Configurar entorno
echo 'JWT_SECRET=cambia-esto-en-produccion' > .env
# 5. Iniciar
bun run src/composition-root.tsEl CLI instala
arckode-frameworkcomo dependencia de tu proyecto automáticamente. No necesitás referenciar rutas del framework — todo se importa comofrom 'arckode-framework'.
Inicio rápido
arckode new mi-api
cd mi-api && bun install
arckode make:auth # módulo completo de auth con JWT
arckode make:module Clientes
arckode analyze # 0 violaciones garantizado
bun run dev # hot reloadConceptos core
Módulo
La unidad básica del sistema. Cada módulo es dueño de sus datos y su lógica. Nunca importa de otro módulo directamente.
modules/productos/
index.ts ← puerta pública (solo exports)
types.ts ← DTOs y ModelDefinition
sockets.ts ← hooks para conectores (opcional)
actions/
service.ts ← lógica de negocio
controller.ts ← capa HTTP (sin lógica)
validators/
schema.ts ← validación de entrada
tests/
service.test.ts// modules/productos/index.ts
export function ProductosModule() {
return createModule({
name: 'productos',
version: '1.0.0',
description: 'Gestión del catálogo de productos',
contract: {
actions: ['listar', 'crear', 'actualizar', 'eliminar'],
events: ['onProductoCreado'],
tables: ['productos'],
},
create({ logger, orm, cache, router }) {
const repo = new OrmRepository<ProductoDTO>(orm, 'Producto')
const service = new ProductosService(repo, logger.child('productos'), cache)
const controller = new ProductosController(service, logger.child('productos'))
router.get('/productos', (req) => controller.index(req))
router.post('/productos', (req) => controller.store(req))
router.put('/productos/:id', (req) => controller.update(req))
router.delete('/productos/:id', (req) => controller.destroy(req))
return service // resolveModule('productos') devuelve esto
},
})
}RepositoryAdapter<T>
Los servicios nunca dependen del ORM directamente. Dependen de la interfaz genérica — el ORM concreto se elige en composition-root.ts.
// ❌ PROHIBIDO — acoplado al ORM, imposible cambiar a MongoDB/Prisma
class ProductosService {
constructor(private orm: ORM) {}
listar() { return this.orm.findMany('Producto') }
}
// ✅ CORRECTO — desacoplado, testeable, intercambiable
class ProductosService {
constructor(private repo: RepositoryAdapter<ProductoDTO>) {}
listar() { return this.repo.findMany() }
}// composition-root.ts — se elige la implementación una sola vez
const repo = new OrmRepository<ProductoDTO>(orm, 'Producto') // SQLite/Postgres
// O mañana:
const repo = new MongoProductoRepo(collection) // MongoDB
// El service nunca cambia.Connector
El único puente entre módulos. Solo delegación — nunca lógica de negocio.
// connectors/pedido-stock.ts
export function conectarPedidoConStock(ctx: ConnectorContext): void {
const productos = ctx.resolveModule<ProductosService>('productos')
ctx.resolveModule('pedidos', {
onPedidoCreado: async (pedido) => {
await productos.descontarStock(pedido.productoId, pedido.cantidad)
},
})
}Composition Root
El único archivo que conoce todo el sistema. La IA lo lee y entiende la arquitectura completa.
// src/composition-root.ts
import { ConfigStore, ORM, Router, NodeServer, MemoryCache, System, Auth, loadEnv } from 'arckode-framework'
import { SqliteAdapter } from 'arckode-framework/adapters/sqlite'
import { jwtTokenAdapter } from 'arckode-framework/adapters/jwt'
import { ProductoModel } from './modules/productos/types'
import { ProductosModule } from './modules/productos'
import { PedidosModule } from './modules/pedidos'
import { conectarPedidoConStock } from './connectors/pedido-stock'
const env = await loadEnv() // carga .env + .env.{NODE_ENV}
const config = new ConfigStore()
config.define({
PORT: { type: 'number', default: 3000 },
DB_PATH: { type: 'string', default: './data/db.sqlite' },
JWT_SECRET: { type: 'string', required: true },
}).load(env)
const db = new SqliteAdapter({ path: config.get('DB_PATH') })
await db.connect()
const orm = new ORM(db)
orm.define('Producto', ProductoModel) // cada módulo es dueño de su ModelDefinition
await orm.migrate()
const system = new System({ config, orm, router, http, cache, auth, ... })
system.addModule(ProductosModule())
system.addModule(PedidosModule())
system.addConnector('pedido-stock', conectarPedidoConStock)
await system.start()CLI — Referencia completa
Proyectos
| Comando | Descripción |
|---|---|
| arckode new <nombre> | Crear proyecto backend completo |
| arckode new:frontend [nombre] | Crear frontend Vue 3 + Vite + TypeScript |
Generadores de backend
| Comando | Descripción |
|---|---|
| arckode make:auth | Módulo de autenticación completo (login, register, JWT, perfil) |
| arckode make:module <Nombre> | Módulo con service, controller, types, validators, tests |
| arckode make:connector <nombre> <mod1> <mod2> | Conector entre módulos |
| arckode make:seed <Nombre> | Seed de datos |
| arckode make:migration <nombre> | Migración SQL con up() y down() |
| arckode make:helper <nombre> | Helper puro (sin efectos secundarios) |
| arckode make:adapter <Adapter> <Interfaz> | Adapter de librería externa |
Generadores de frontend
| Comando | Descripción |
|---|---|
| arckode make:page <Nombre> | Módulo frontend (API client + composable + página + router) |
| arckode generate:api [frontend-path] | Genera API clients desde módulos del backend |
Base de datos
| Comando | Descripción |
|---|---|
| arckode db:migrate | Ejecutar migraciones pendientes (src/migrations/) |
| arckode db:migrate down | Revertir la última migración |
| arckode db:seed | Listar seeds disponibles |
Análisis y diagnóstico
| Comando | Descripción |
|---|---|
| arckode analyze | Detecta 15+ tipos de violaciones de arquitectura |
| arckode routes | Lista todas las rutas registradas (análisis estático) |
Adapters
Base de datos
// SQLite — desarrollo y apps de baja escala
import { SqliteAdapter } from 'arckode-framework/adapters/sqlite'
const db = new SqliteAdapter({ path: './data/app.sqlite' })
// PostgreSQL — producción
import { PostgresAdapter } from 'arckode-framework/adapters/postgres'
const db = new PostgresAdapter({ connectionString: process.env.DATABASE_URL, poolMax: 10 })Cache
// En memoria — desarrollo (se pierde al reiniciar)
const cache = new MemoryCache()
// Redis — producción
import { RedisCacheAdapter } from 'arckode-framework/adapters/redis-cache'
const cache = new RedisCacheAdapter({ url: process.env.REDIS_URL })
await cache.connect()Módulos opcionales
Queue
import { QueueService, MemoryQueueAdapter } from 'arckode-framework/queue'
const queue = new QueueService(new MemoryQueueAdapter())
queue.register('enviar-email', async (job) => {
await mail.send(job.data as EmailData)
})
await queue.dispatch('enviar-email', { to: '[email protected]', subject: 'Bienvenido' })
await queue.dispatch('enviar-email', { ... }, { delay: 5000, maxAttempts: 3 })Events (pub-sub)
import { EventBus } from 'arckode-framework/events'
const events = new EventBus()
events.on('pedido.creado', async (pedido) => { ... })
events.emit('pedido.creado', pedido)WebSockets
import { WsServer } from 'arckode-framework/ws'
const ws = new WsServer()
ws.on('connection', (client) => { client.send({ type: 'welcome' }) })
ws.broadcast({ type: 'stock-bajo', productoId: id })import { MailService } from 'arckode-framework/mail'
import { SmtpAdapter } from 'arckode-framework/mail/smtp'
const mail = new MailService(new SmtpAdapter({ host: 'smtp.gmail.com', port: 587, ... }))
await mail.send({ to: '[email protected]', subject: 'Bienvenido', html: '<p>Hola</p>' })Storage
import { StorageService } from 'arckode-framework/storage'
import { LocalAdapter } from 'arckode-framework/storage/local'
const storage = new StorageService(new LocalAdapter({ path: './uploads' }))
const url = await storage.save('avatars/user.png', fileBuffer)Middlewares
import { cors, rateLimit, requestLogger, timeout, compression, bodyLimit } from 'arckode-framework/middlewares'
// Globales
router.use(cors({ origins: ['https://miapp.com'] }))
router.use(rateLimit({ windowMs: 60_000, max: 100 }))
router.use(requestLogger(logger))
router.use(compression())
// Por ruta
router.get('/admin', handler, [auth.authenticate('admin'), timeout(3000)])
router.post('/upload', handler, [bodyLimit(10 * 1024 * 1024)]) // 10MBAuth
const auth = new Auth(jwtTokenAdapter, process.env.JWT_SECRET, logger)
// Crear token
const token = await auth.createToken({ id: user.id, role: 'admin' })
// Hashear password (scrypt — sin dependencias externas)
const hash = await auth.hashPassword(password)
const ok = await auth.comparePassword(password, hash)
// Proteger rutas
router.get('/perfil', handler, [auth.authenticate()]) // cualquier usuario autenticado
router.get('/admin', handler, [auth.authenticate('admin')]) // solo admins
router.delete('/users/:id', handler, [auth.authenticate('admin', 'superadmin')])Variables de entorno
loadEnv() carga .env base y .env.{NODE_ENV} con override por stage. process.env siempre tiene prioridad máxima.
# .env
PORT=3000
DB_PATH=./data/dev.sqlite
LOG_LEVEL=debug
# .env.production
DB_PATH=./data/prod.sqlite
LOG_LEVEL=warnNODE_ENV=production bun run src/composition-root.ts
# Lee .env → sobrescribe con .env.production → process.env tiene prioridadTesting
import { createTestClient, createRecordingOrm } from 'arckode-framework/testing'
import { OrmRepository } from 'arckode-framework'
// ORM que registra llamadas en memoria — sin base de datos real
const orm = createRecordingOrm()
const repo = new OrmRepository<ProductoDTO>(orm, 'Producto')
const service = new ProductosService(repo, logger, cache)
// Cliente HTTP que hace requests al Router sin levantar un server real
const client = createTestClient(router)
test('listar productos vacío', async () => {
const res = await client.get('/productos')
expect(res.status).toBe(200)
expect(res.body).toEqual([])
})
test('crear producto', async () => {
const res = await client.post('/productos', {
body: { nombre: 'Laptop', precio: 1500, stock: 10 },
})
expect(res.status).toBe(201)
expect(res.body.nombre).toBe('Laptop')
})Análisis de arquitectura
arckode analyze══════════════════════════════════════════════
Arckode — Análisis de Arquitectura
══════════════════════════════════════════════
VIOLACIONES ENCONTRADAS: 2
[Acoplamiento]
❌ modules/pedidos/actions/service.ts:12
Importa directamente de otro módulo (CLAUDE #1)
→ Usar un conector en /connectors/
[Portabilidad]
❌ modules/clientes/actions/service.ts:8
El service inyecta ORM directamente (CLAUDE #18)
→ Usar RepositoryAdapter<ClienteDTO>| Categoría | Violations detectadas |
|---|---|
| Estructura | MISSING_INDEX, MISSING_TYPES, MISSING_SERVICE, MISSING_CONTROLLER, MISSING_TESTS |
| Acoplamiento | DIRECT_MODULE_IMPORT |
| Diseño | BUSINESS_LOGIC_IN_CONTROLLER, CONTROLLER_MISSING_VALIDATION, EMPTY_MODULE_DESCRIPTION |
| Calidad | TESTS_WITHOUT_CASES, GOD_SERVICE |
| Seguridad | IDOR_RISK, HARDCODED_SECRET, INSECURE_PASSWORD |
| Performance | N_PLUS_ONE_RISK |
| Portabilidad | SERVICE_DEPENDS_ON_ORM |
Ejemplos incluidos
| Ejemplo | Descripción |
|---|---|
| examples/ecommerce/ | Productos, pedidos, conector de stock, auth |
| examples/completo/ | Auth, productos, pedidos, mail, storage, queue, WebSockets |
Documentación adicional
| Archivo | Contenido |
|---|---|
| CLAUDE.md | 18 reglas inmutables — el contrato que la IA sigue |
| kernel/framework.ts | Fuente completa del kernel (la IA lo lee entero) |
| kernel/testing.ts | Utilidades de testing |
| kernel/middlewares.ts | Middlewares disponibles |
Cómo funciona la distribución
Para usuarios del framework
# Instalar CLI una vez
bun install -g arckode-framework
# Crear proyecto — el CLI genera todo: composition-root.ts, CLAUDE.md, .env, package.json
arckode new mi-tienda --db=postgres
# CLAUDE.md se genera automáticamente en tu proyecto con las 18 reglas del framework.
# La IA lo lee primero antes de escribir cualquier línea de código.
# Actualizar el framework en tu proyecto
bun update arckode-frameworkPara la IA (flujo de trabajo)
Cuando la IA trabaja en un proyecto con Arckode, hace esto en orden:
1. Lee CLAUDE.md → entiende las 18 reglas que NO puede violar
2. Lee composition-root.ts → entiende todos los módulos, conectores y dependencias
3. Corre arckode analyze → verifica que el estado actual tiene 0 violaciones
4. Genera código → siguiendo la estructura exacta del framework
5. Corre arckode analyze → verifica que sus cambios no introdujeron violaciones
6. Corre bun test → verifica que nada se rompióPara el autor del framework (actualizaciones)
# 1. Hacer los cambios
# 2. Correr tests y typecheck
bun test && bun run typecheck
# 3. Subir la versión en package.json (semver)
# 1.0.0 → 1.0.1 patch: bugfix
# 1.0.0 → 1.1.0 minor: feature nueva, retrocompatible
# 1.0.0 → 2.0.0 major: breaking change
# 4. Publicar
npm publish
# Los usuarios actualizan corriendo en su proyecto:
bun update arckode-frameworkAdapters opcionales
Arckode solo instala lo que usás. Cada adapter tiene su dependencia peer:
| Adapter | Instalar |
|---|---|
| SQLite | bun add better-sqlite3 |
| PostgreSQL | bun add pg |
| MySQL | bun add mysql2 |
| Redis (cache) | bun add redis |
| Mail (SMTP) | bun add nodemailer |
El framework core (kernel/framework.ts) no depende de ninguna librería externa — solo de Node.js/Bun nativo.
Licencia
MIT
