arckode-framework 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/analyze.ts CHANGED
@@ -42,6 +42,9 @@ export interface Violation {
42
42
  | 'IDOR_RISK' // findById sin assertOwnership posterior
43
43
  // ── Portabilidad ──
44
44
  | 'SERVICE_DEPENDS_ON_ORM' // service inyecta ORM en lugar de RepositoryAdapter
45
+ // ── Estructura de archivos (Regla #22) ──
46
+ | 'MODEL_IN_TYPES_FILE' // ModelDefinition en types.ts en vez de model.ts
47
+ | 'MISSING_MODEL_TS' // módulo sin model.ts
45
48
  // ── Conectores ──
46
49
  | 'DUPLICATE_CONNECTOR' // mismo nombre lógico con distinto casing (foo-bar.ts y fooBar.ts)
47
50
  | 'UNREGISTERED_CONNECTOR' // archivo existe pero no está en composition-root.ts
@@ -427,7 +430,6 @@ export async function analyzeProject(basePath: string): Promise<AnalysisResult>
427
430
  // 9. SERVICE DEPENDS ON ORM — service inyecta ORM en lugar de RepositoryAdapter (Regla #18)
428
431
  try {
429
432
  const serviceContent = await readFile(servicePath, 'utf-8')
430
- // Detectar: private orm: ORM o private readonly orm: ORM en el constructor
431
433
  if (/private\s+(readonly\s+)?\w+\s*:\s*ORM\b/.test(serviceContent)) {
432
434
  violations.push({
433
435
  type: 'SERVICE_DEPENDS_ON_ORM',
@@ -436,6 +438,28 @@ export async function analyzeProject(basePath: string): Promise<AnalysisResult>
436
438
  })
437
439
  }
438
440
  } catch { /* no service */ }
441
+
442
+ // 10. MODEL IN TYPES — ModelDefinition en types.ts en vez de model.ts (Regla #22)
443
+ const typesFilePath = join(modPath, 'types.ts')
444
+ try {
445
+ const typesContent = await readFile(typesFilePath, 'utf-8')
446
+ if (/\bModelDefinition\b/.test(typesContent)) {
447
+ violations.push({
448
+ type: 'MODEL_IN_TYPES_FILE',
449
+ module: module.name,
450
+ message: `"${module.name}/types.ts" contiene ModelDefinition o schema de DB. Moverlo a model.ts — son conceptos distintos. (CLAUDE #22)`,
451
+ })
452
+ }
453
+ } catch { /* no types.ts */ }
454
+
455
+ // 11. MISSING MODEL.TS — módulo sin model.ts (Regla #22)
456
+ if (!await fileExists(join(modPath, 'model.ts'))) {
457
+ violations.push({
458
+ type: 'MISSING_MODEL_TS',
459
+ module: module.name,
460
+ message: `"${module.name}" no tiene model.ts. El schema de DB debe vivir separado de types.ts. (CLAUDE #22)`,
461
+ })
462
+ }
439
463
  }
440
464
 
441
465
  return {
@@ -525,7 +549,7 @@ export function printAnalysis(result: AnalysisResult): void {
525
549
  }
526
550
 
527
551
  for (const v of result.violations) {
528
- if (['MISSING_INDEX', 'MISSING_SERVICE', 'MISSING_TYPES', 'MISSING_SOCKETS', 'MISSING_VALIDATORS', 'MISSING_TESTS', 'MISSING_CONNECTORS', 'EMPTY_MODULE_DESCRIPTION'].includes(v.type)) {
552
+ if (['MISSING_INDEX', 'MISSING_SERVICE', 'MISSING_TYPES', 'MISSING_SOCKETS', 'MISSING_VALIDATORS', 'MISSING_TESTS', 'MISSING_CONNECTORS', 'EMPTY_MODULE_DESCRIPTION', 'MODEL_IN_TYPES_FILE', 'MISSING_MODEL_TS'].includes(v.type)) {
529
553
  byCategory['Estructura']!.push(v)
530
554
  } else if (['DIRECT_MODULE_IMPORT', 'SERVICE_IMPORTS_OTHER_MODULE', 'CONNECTOR_BUSINESS_LOGIC'].includes(v.type)) {
531
555
  byCategory['Acoplamiento']!.push(v)
@@ -616,6 +640,8 @@ export function buildManifest(result: AnalysisResult): Record<string, unknown> {
616
640
  'REGLA 6: Todo POST/PUT/PATCH requiere validateSchema().',
617
641
  'REGLA 7: Controller NO llama al ORM. Llama al service.',
618
642
  'REGLA 18: Service recibe RepositoryAdapter<T>, no ORM directamente.',
643
+ 'REGLA 22: model.ts separado de types.ts. Schema DB ≠ contrato TS.',
644
+ 'REGLA 23: Un DEFAULT por caso. Escalar SOLO cuando la condición lo justifica. Nivel 1: OrmRepository / service.ts / Conector. Nivel 2+ solo si aplica.',
619
645
  ],
620
646
  }
621
647
  }
package/cli/index.ts CHANGED
@@ -168,12 +168,12 @@ async function main() {
168
168
  version: '1.0.0',
169
169
  type: 'module',
170
170
  scripts: {
171
- dev: `bun --watch src/composition-root.ts`,
172
- start: `bun run src/composition-root.ts`,
173
- test: `bun test`,
174
- 'test:watch': `bun test --watch`,
175
- analyze: `bun run node_modules/arckode-framework/cli/index.ts analyze`,
176
- 'analyze:json': `bun run node_modules/arckode-framework/cli/index.ts analyze --json`,
171
+ dev: `bun --watch src/composition-root.ts`,
172
+ start: `bun run src/composition-root.ts`,
173
+ test: `bun test`,
174
+ 'test:watch': `bun test --watch`,
175
+ analyze: `bun run node_modules/arckode-framework/cli/index.ts analyze`,
176
+ 'analyze:ci': `bun run node_modules/arckode-framework/cli/index.ts analyze --ci`,
177
177
  },
178
178
  dependencies: {
179
179
  'arckode-framework': 'latest',
@@ -260,14 +260,18 @@ data/
260
260
  const basePath = process.cwd()
261
261
  const result = await analyzeProject(basePath)
262
262
 
263
- if (args.includes('--json')) {
264
- const manifest = buildManifest(result)
265
- const dest = join(basePath, 'arckode.json')
266
- await writeFile(dest, JSON.stringify(manifest, null, 2))
267
- const status = result.valid ? '✅ válido' : `❌ ${result.violations.length} violaciones`
268
- console.log(`arckode.json generado — ${status}`)
263
+ // arckode.json siempre se regenera — es el snapshot que la IA lee primero
264
+ const manifest = buildManifest(result)
265
+ const dest = join(basePath, 'arckode.json')
266
+ await writeFile(dest, JSON.stringify(manifest, null, 2))
267
+
268
+ if (args.includes('--ci')) {
269
+ // Modo CI: sin output de consola, exit 1 si hay violaciones
270
+ if (!result.valid) process.exit(1)
269
271
  } else {
270
272
  printAnalysis(result)
273
+ const status = result.valid ? '✅ sin violaciones' : `❌ ${result.violations.length} violación(es)`
274
+ console.log(`→ arckode.json actualizado — ${status}\n`)
271
275
  }
272
276
  break
273
277
  }
@@ -62,7 +62,8 @@ src/modules/mi-modulo/
62
62
  | 19 | Transactor para atomicidad multi-tabla — \`transactor.run(async (repos) => ...)\` | manual |
63
63
  | 20 | Conectores limpios: kebab-case, con prefijo \`connect\`, registrados en composition-root | \`arckode analyze\` |
64
64
  | 21 | Si el service repite filtros 3+ veces o necesita JOIN/IN/LIKE → crear \`repository.ts\` | manual |
65
- | 22 | \`model.ts\` separado de \`types.ts\` (schema DB ≠ contrato TS) | manual |
65
+ | 22 | \`model.ts\` separado de \`types.ts\` (schema DB ≠ contrato TS) | \`arckode analyze\` |
66
+ | 23 | Un DEFAULT por caso. Escalar SOLO cuando la condición lo justifica | \`arckode analyze\` |
66
67
 
67
68
  ---
68
69
 
@@ -152,11 +153,22 @@ seeds/ ← datos de prueba
152
153
  migrations/ ← migraciones explícitas de DB
153
154
  \`\`\`
154
155
 
156
+ ## Escalation ladder — un DEFAULT por caso
157
+
158
+ \`\`\`
159
+ Capa de datos: OrmRepository<T> → repository.ts custom → Transactor
160
+ Lógica de negocio: service.ts → usecases/{caso}.ts (nunca para CRUD)
161
+ Entre módulos: Conector → EventBus (2+ módulos reaccionan)
162
+ DB Adapter: SqliteAdapter → MySQL / Postgres (concurrencia alta)
163
+ \`\`\`
164
+
165
+ Escalar SOLO cuando la condición lo justifica. Si no hay condición → nivel 1.
166
+
155
167
  ## Comandos útiles
156
168
  \`\`\`bash
157
169
  bun run dev # desarrollo con hot reload
158
- bun run analyze # verificar arquitectura (consola)
159
- bun run analyze:json # generar/actualizar arckode.json
170
+ bun run analyze # verificar arquitectura + actualiza arckode.json
171
+ bun run analyze:ci # modo CI: exit 1 si hay violaciones
160
172
  bun arckode make:module Nombre # generar módulo completo
161
173
  bun arckode make:connector nombre-x mod1 mod2 # conectar dos módulos (kebab-case)
162
174
  bun test # correr todos los tests
@@ -166,9 +178,10 @@ bun test # correr todos los tests
166
178
 
167
179
  ## Notas para la IA
168
180
 
169
- - Si el módulo es CRUD simple: NO crees \`repository.ts\` ni \`usecases/\`. Pasás \`OrmRepository<T>\` directo al service.
170
- - Si el módulo tiene 3+ tablas: definí TODOS los modelos en \`model.ts\` y registralos en \`registerXxxModels(orm)\` (helper único). El \`index.ts\` queda limpio.
171
- - Si necesitás atomicidad: inyectá \`Transactor\` al service, no el ORM. Mantiene la dependency inversion.
172
- - Si el analyzer muestra \`LEGACY_ACTIONS_FOLDER\`, \`DUPLICATE_CONNECTOR\` o \`UNREGISTERED_CONNECTOR\`: arreglarlos ANTES de pushear código nuevo.
181
+ - CRUD simple: usá \`OrmRepository<T>\` directo. NO crees \`repository.ts\` ni \`usecases/\`.
182
+ - 3+ tablas en un módulo: definí todos los modelos en \`model.ts\`, registralos en \`registerXxxModels(orm)\`.
183
+ - Atomicidad multi-tabla: inyectá \`Transactor\` al service, no el ORM.
184
+ - Antes de escribir código: revisá \`arckode.json\` (violations) y \`composition-root.ts\` (estado actual).
185
+ - Si el analyzer muestra \`LEGACY_ACTIONS_FOLDER\`, \`DUPLICATE_CONNECTOR\`, \`UNREGISTERED_CONNECTOR\`, \`MODEL_IN_TYPES_FILE\` o \`MISSING_MODEL_TS\`: arreglarlos ANTES de agregar código nuevo.
173
186
  `
174
187
  }
@@ -487,9 +487,7 @@ ${sampleData.replace(`'${p.name} de ejemplo'`, `'${p.name} de ejemplo 2'`).repla
487
487
  },
488
488
  ]
489
489
 
490
- for (const item of items) {
491
- await orm.create('${p.name}', item)
492
- }
490
+ await Promise.all(items.map(item => orm.create('${p.name}', item)))
493
491
 
494
492
  console.log(' ✓ ${p.name} seeded: ' + items.length + ' items')
495
493
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arckode-framework",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "AI-first TypeScript/Bun framework. Modular, SOLID, zero magic. The AI reads the composition root and knows everything.",
5
5
  "type": "module",
6
6
  "main": "./kernel/framework.ts",
@@ -60,7 +60,7 @@
60
60
  "peerDependencies": {
61
61
  "mysql2": ">=3.0.0",
62
62
  "pg": "^8.0.0",
63
- "redis": ">=4.0.0",
63
+ "redis": "",
64
64
  "nodemailer": ">=6.0.0"
65
65
  },
66
66
  "peerDependenciesMeta": {
@@ -83,6 +83,7 @@
83
83
  "@types/pg": "^8.11.0",
84
84
  "bun-types": "^1.3.14",
85
85
  "pg": "^8.0.0",
86
+ "redis": ">=4.0.0",
86
87
  "typescript": "^5.6.0"
87
88
  },
88
89
  "keywords": [