@gzl10/nexus-backend 0.12.7 → 0.13.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/AGENTS.md ADDED
@@ -0,0 +1,386 @@
1
+ # Backend AGENTS.md
2
+
3
+ `@gzl10/nexus-backend` - BaaS library (Express 5 + Knex + CASL)
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ pnpm dev # Hot reload (tsx watch)
9
+ pnpm build # Build (tsup)
10
+ pnpm typecheck # Type check
11
+ pnpm test # Vitest
12
+
13
+ # Database migrations
14
+ pnpm migrate create <name> # Generate migration from entities
15
+ pnpm migrate up # Run pending migrations
16
+ pnpm migrate down # Rollback last batch
17
+ pnpm migrate status # Show migration status
18
+ pnpm migrate reset # Reset DB (dev only)
19
+ ```
20
+
21
+ ## Directory Structure
22
+
23
+ ```text
24
+ src/
25
+ ├── config/ # Env vars, NexusConfig, DB config (NO lógica)
26
+ ├── db/ # Conexión Knex, sistema de migraciones, SQL utils
27
+ │ ├── migrations/ # Archivos de migración versionados (.ts)
28
+ │ ├── migration-runner.ts # Ejecutor con batches
29
+ │ ├── migration-generator.ts # Generador automático
30
+ │ ├── migration-lock.ts # Sistema de bloqueo
31
+ │ ├── schema-reader.ts # Lector de esquema DB
32
+ │ └── migration-helpers.ts # Utilidades Knex
33
+ ├── engine/ # Registro módulos, contexto, carga
34
+ ├── runtime/ # Services, controllers, factories para entities
35
+ ├── core/ # Express app, server, middleware, Socket.IO
36
+ └── modules/ # Lógica de negocio (entities, routes, services)
37
+ ```
38
+
39
+ ### Reglas de Dependencias
40
+
41
+ - \`modules/\` → NO importa de otros modules (usa \`ctx.services\`)
42
+ - \`engine/\` → NO importa de modules (solo tipos genéricos)
43
+ - \`config/\` → Solo configuración, NO conexiones ni lógica
44
+ - \`db/connection.ts\` → Gestión conexión Knex (separado de config)
45
+
46
+ ## Database Migrations
47
+
48
+ Sistema estilo Prisma con generación automática de migraciones desde entity definitions.
49
+
50
+ ### Workflow
51
+
52
+ **Desarrollo rápido:**
53
+ 1. Modificar entities en \`src/modules/*/\`
54
+ 2. \`pnpm migrate dev\` - Genera y aplica migración automáticamente
55
+
56
+ **Desarrollo con revisión:**
57
+ 1. Modificar entities en \`src/modules/*/\`
58
+ 2. \`pnpm migrate create add_email_field\` - Genera migración comparando entities vs schema actual
59
+ 3. Revisar archivo generado en \`migrations/{timestamp}_add_email_field.ts\`
60
+ 4. \`pnpm migrate up\` - Ejecutar migraciones pendientes
61
+ 5. \`pnpm migrate down\` - Rollback si es necesario
62
+
63
+ ### Archivos de Migración
64
+
65
+ Formato: \`{timestamp}_{name}.ts\`
66
+
67
+ \`\`\`typescript
68
+ import type { Knex } from 'knex'
69
+
70
+ export async function up(knex: Knex): Promise<void> {
71
+ await knex.schema.createTable('users', (table) => {
72
+ table.string('id', 26).primary()
73
+ table.string('email', 255).notNullable().unique()
74
+ table.timestamp('created_at').defaultTo(knex.fn.now())
75
+ })
76
+ }
77
+
78
+ export async function down(knex: Knex): Promise<void> {
79
+ await knex.schema.dropTableIfExists('users')
80
+ }
81
+ \`\`\`
82
+
83
+ ### Sistema de Tracking
84
+
85
+ - Tabla \`_nexus_migrations\`: registra migraciones ejecutadas con batch, status, execution_time
86
+ - Tabla \`_nexus_migration_lock\`: previene ejecución concurrente (detecta PIDs muertos)
87
+ - Batches: agrupa migraciones ejecutadas juntas para rollback coordinado
88
+
89
+ ### Producción
90
+
91
+ Migraciones se ejecutan automáticamente en startup del servidor:
92
+
93
+ \`\`\`typescript
94
+ await ensureMigrationTables(getDb())
95
+ await runMigrations() // Falla = servidor no inicia
96
+ \`\`\`
97
+
98
+ ### UI
99
+
100
+ Entity \`migration-history\` en módulo \`system\` (solo lectura, ADMIN/SUPPORT):
101
+ - Ver historial completo de migraciones
102
+ - Status: completed, failed, rolled_back
103
+ - Tiempos de ejecución, errores, batches
104
+
105
+ ## Module Structure
106
+
107
+ ```text
108
+ src/modules/{name}/
109
+ ├── index.ts # Manifest + barrel exports (tipos incluidos)
110
+ ├── {name}.entity.ts # EntityDefinitions
111
+ ├── {name}.types.ts # Tipos + ServiceInterface
112
+ ├── {name}.service.ts # Lógica de negocio
113
+ └── {name}.routes.ts # Rutas custom (opcional)
114
+ ```
115
+
116
+ **Barrel exports obligatorios:** Tipos de servicio exportados desde `index.ts`
117
+
118
+ ## Actions Organization
119
+
120
+ Cuando un módulo tiene múltiples actions, organizarlas en carpeta `actions/`:
121
+
122
+ ```text
123
+ src/modules/{name}/
124
+ ├── actions/
125
+ │ ├── helpers.ts # Funciones compartidas (cookies, request info, rate limits)
126
+ │ ├── {action-name}.action.ts # Un archivo por action
127
+ │ └── index.ts # Re-exports + array de actions
128
+ ├── index.ts
129
+ └── ...
130
+ ```
131
+
132
+ **Estructura de un action file:**
133
+
134
+ ```typescript
135
+ // login.action.ts
136
+ import type { ActionDefinition } from '@gzl10/nexus-sdk'
137
+ import { createAuthService } from '../auth.service.js'
138
+ import { getCookieOptions, getRequestInfo, createRateLimits } from './helpers.js'
139
+
140
+ export const loginAction: ActionDefinition = {
141
+ type: 'action',
142
+ key: 'login', // Ruta: /api/v1/{module}/{key}
143
+ label: { en: 'Login', es: 'Iniciar sesión' },
144
+ icon: 'mdi:login',
145
+ scope: 'module', // module | entity | row
146
+ method: 'POST',
147
+ middleware: (ctx) => createRateLimits(ctx).login,
148
+ fields: {
149
+ email: { name: 'email', input: 'email', required: true, label: { en: 'Email' } },
150
+ password: { name: 'password', input: 'password', required: true, label: { en: 'Password' } }
151
+ },
152
+ handler: async (ctx, input, req, res) => {
153
+ const body = input as LoginInput
154
+ const authService = createAuthService(ctx)
155
+ const result = await authService.login(body, getRequestInfo(req!))
156
+ res!.cookie('refreshToken', result.refreshToken, getCookieOptions(req!))
157
+ return { user: result.user, accessToken: result.accessToken }
158
+ }
159
+ }
160
+ ```
161
+
162
+ **Scope routing:**
163
+
164
+ | Scope | Ruta resultante |
165
+ | -------- | --------------------------------------- |
166
+ | `module` | `/api/v1/{module}/{action}` |
167
+ | `entity` | `/api/v1/{module}/{entity}/{action}` |
168
+ | `row` | `/api/v1/{module}/{entity}/:id/{action}`|
169
+
170
+ **actions/index.ts:**
171
+
172
+ ```typescript
173
+ import type { ActionDefinition } from '@gzl10/nexus-sdk'
174
+ export { loginAction } from './login.action.js'
175
+ export { logoutAction } from './logout.action.js'
176
+ // ...
177
+
178
+ import { loginAction } from './login.action.js'
179
+ import { logoutAction } from './logout.action.js'
180
+
181
+ export const authActions: Array<ActionDefinition & { type: 'action' }> = [
182
+ loginAction,
183
+ logoutAction
184
+ ] as Array<ActionDefinition & { type: 'action' }>
185
+ ```
186
+
187
+ **En module/index.ts:**
188
+
189
+ ```typescript
190
+ import { authActions } from './actions/index.js'
191
+
192
+ export const authModule: ModuleManifest = {
193
+ definitions: [
194
+ someEntity,
195
+ ...authActions // Se auto-montan y documentan en OpenAPI
196
+ ]
197
+ }
198
+ ```
199
+
200
+ ## Patterns
201
+
202
+ **Comunicación entre módulos:**
203
+
204
+ ```typescript
205
+ // En init(): registrar
206
+ ctx.services['users'] = createUsersService(ctx)
207
+
208
+ // En otro módulo: usar (tipar con import del módulo)
209
+ const users = ctx.services['users'] as UsersService
210
+ ```
211
+
212
+ **ESM imports:** Siempre con extensión `.js`
213
+
214
+ **Knex count:** `.first<{ count: string | number }>()`
215
+
216
+ **EventEmitter2:** `import pkg from 'eventemitter2'; const { EventEmitter2 } = pkg`
217
+
218
+ ## CASL Authorization
219
+
220
+ Roles: `OWNER`, `ADMIN`, `MANAGER`, `EDITOR`, `CONTRIBUTOR`, `USER`, `VIEWER`, `SUPPORT`, `AUDITOR`, `DEVELOPER`, `BOT`, `SERVICE`
221
+
222
+ Actions: `create`, `read`, `update`, `delete`, `manage`
223
+
224
+ ## Testing
225
+
226
+ `tests/runtime/` - Unit tests con `createMockContext()` de `test-helpers.ts`
227
+
228
+ `tests/migration-*.test.ts` - Tests del sistema de migraciones (SQLite, PostgreSQL, MySQL)
229
+
230
+ ## Ejemplos de Uso
231
+
232
+ ### Entity Definitions
233
+
234
+ ```typescript
235
+ // reference entity - catálogos estáticos
236
+ export const categories: ReferenceEntityDefinition = {
237
+ key: 'categories',
238
+ type: 'reference',
239
+ labelField: 'name',
240
+ fields: {
241
+ name: { type: 'text', required: true },
242
+ code: { type: 'text', required: true }
243
+ },
244
+ seed: [
245
+ { name: 'Technology', code: 'tech' },
246
+ { name: 'Science', code: 'sci' }
247
+ ]
248
+ }
249
+
250
+ // single entity - configuración singleton
251
+ export const siteConfig: SingleEntityDefinition = {
252
+ key: 'site_config',
253
+ type: 'single',
254
+ defaults: {
255
+ siteName: 'My App',
256
+ logo: '/logo.png',
257
+ theme: 'light'
258
+ }
259
+ }
260
+
261
+ // tree entity - estructura jerárquica
262
+ export const departments: TreeEntityDefinition = {
263
+ key: 'departments',
264
+ type: 'tree',
265
+ labelField: 'name',
266
+ fields: {
267
+ name: { type: 'text', required: true },
268
+ code: { type: 'text' }
269
+ },
270
+ seed: [
271
+ { id: '1', name: 'Engineering', parent_id: null },
272
+ { id: '2', name: 'Frontend', parent_id: '1' }
273
+ ]
274
+ }
275
+
276
+ // dag entity - grafo acíclico dirigido
277
+ export const workflows: DagEntityDefinition = {
278
+ key: 'workflows',
279
+ type: 'dag',
280
+ labelField: 'name',
281
+ fields: {
282
+ name: { type: 'text', required: true },
283
+ status: { type: 'select', options: ['pending', 'active', 'done'] }
284
+ }
285
+ }
286
+ ```
287
+
288
+ ### Service Usage
289
+
290
+ ```typescript
291
+ // SingleService - get/set config
292
+ const configService = new SingleService(ctx, siteConfig)
293
+ const config = await configService.get() // Returns defaults if not in DB
294
+ await configService.set({ siteName: 'New Name' }) // Merge update
295
+ await configService.reset() // Reset to defaults
296
+
297
+ // TreeService - hierarchical operations
298
+ const deptService = new TreeService(ctx, departments)
299
+ const children = await deptService.findChildren('1')
300
+ const ancestors = await deptService.findAncestors('2')
301
+ const tree = await deptService.findTree() // Full tree structure
302
+
303
+ // DagService - graph operations
304
+ const workflowService = new DagService(ctx, workflows)
305
+ await workflowService.addEdge('node1', 'node2')
306
+ const deps = await workflowService.findDependencies('node2')
307
+ const deps = await workflowService.findDependents('node1')
308
+ ```
309
+
310
+ ### Seed Configuration
311
+
312
+ ```typescript
313
+ // Inline array (simple)
314
+ seed: [{ name: 'Item 1' }, { name: 'Item 2' }]
315
+
316
+ // SeedConfig object (advanced)
317
+ seed: {
318
+ source: 'url',
319
+ url: 'https://api.example.com/data.json',
320
+ headers: { 'Authorization': 'Bearer token' }
321
+ }
322
+
323
+ // seed() method persists data to DB at startup
324
+ const service = new ReferenceService(ctx, definition)
325
+ const count = await service.seed() // Returns number of records seeded
326
+ ```
327
+
328
+ ## Plugin Store
329
+
330
+ Sistema de gestión de plugins desde la UI. Permite instalar, desinstalar y activar/desactivar plugins en caliente.
331
+
332
+ ### Entidades
333
+
334
+ ```typescript
335
+ // Estado de activación de plugins (persistido en DB)
336
+ pluginStateEntity: SingleEntityDefinition // system module
337
+ // key: 'plugin_state', defaults: { plugins: {} }
338
+ // plugins: Record<string, boolean> - packageName -> enabled
339
+
340
+ // Catálogo de plugins disponibles en npm
341
+ pluginStoreEntity: ExternalEntityDefinition // system module
342
+ // key: 'plugin_store'
343
+ // Obtiene datos de npm registry, no persiste en DB
344
+ ```
345
+
346
+ ### Actions
347
+
348
+ | Action | Scope | Descripción |
349
+ | ------ | ----- | ----------- |
350
+ | `install-plugin` | module | Instala plugin desde npm (`pnpm add`) |
351
+ | `uninstall-plugin` | module | Desinstala plugin (`pnpm remove`) |
352
+ | `toggle-plugin` | module | Activa/desactiva plugin (actualiza `plugin_state`) |
353
+
354
+ ### Servicio
355
+
356
+ ```typescript
357
+ // NpmRegistryService - consulta npm registry
358
+ const npmService = new NpmRegistryService(ctx)
359
+ const packages = await npmService.searchPlugins() // Busca @gzl10/nexus-plugin-*
360
+ const details = await npmService.getPackageDetails('@gzl10/nexus-plugin-ai')
361
+ ```
362
+
363
+ ### Helper
364
+
365
+ ```typescript
366
+ // shouldLoadPlugin - determina si cargar un plugin
367
+ import { shouldLoadPlugin } from './system.helpers.js'
368
+
369
+ // Retorna true si:
370
+ // 1. Plugin está instalado (existe en node_modules)
371
+ // 2. Plugin no está explícitamente desactivado en plugin_state
372
+ const load = await shouldLoadPlugin(ctx, '@gzl10/nexus-plugin-ai')
373
+ ```
374
+
375
+ ### Configuración
376
+
377
+ ```bash
378
+ # Habilitar instalación de plugins desde UI (deshabilitado por defecto)
379
+ NEXUS_ALLOW_PLUGIN_INSTALL=true
380
+ ```
381
+
382
+ Cuando `NEXUS_ALLOW_PLUGIN_INSTALL=false`, las acciones `install-plugin` y `uninstall-plugin` retornan error 403.
383
+
384
+ ## Referencia detallada
385
+
386
+ Ver KB Notion: "Backend Files Reference" y "Patrones" para documentación completa.
package/README.md CHANGED
@@ -153,18 +153,17 @@ await start({
153
153
  ## CLI
154
154
 
155
155
  ```bash
156
- npx nexus start # Start server
157
- npx nexus migrate # Run migrations
158
- npx nexus seed # Seed database
159
- ```
160
-
161
- ## Development
162
-
163
- ```bash
164
- pnpm dev # Start dev server
165
- pnpm build # Build for production
166
- pnpm typecheck # Type check
167
- pnpm test # Run tests
156
+ # Database migrations
157
+ pnpm migrate dev # Generate and apply migration (like Prisma)
158
+ pnpm migrate create <name> # Generate migration from entities
159
+ pnpm migrate up # Run pending migrations
160
+ pnpm migrate down # Rollback last batch
161
+ pnpm migrate status # Show migration status
162
+ pnpm migrate reset # Reset database (dev only)
163
+
164
+ # Development
165
+ pnpm dev # Start dev server
166
+ pnpm seed # Seed database
168
167
  ```
169
168
 
170
169
  ## Stack