@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 +386 -0
- package/README.md +11 -12
- package/dist/cli.js +20757 -20
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1379 -260
- package/dist/index.js +19428 -11536
- package/dist/index.js.map +1 -1
- package/dist/instrumentation.d.ts +24 -0
- package/dist/instrumentation.js +78 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +21744 -0
- package/dist/main.js.map +1 -0
- package/package.json +63 -33
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
pnpm
|
|
166
|
-
pnpm
|
|
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
|