@gzl10/nexus-backend 0.1.4

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/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # nexus-backend
2
+
3
+ > **Warning**: This project is currently in testing/experimental phase. Use at your own risk.
4
+
5
+ Backend as a Service (BaaS) with Express 5, Knex and CASL. Ready to use as an npm library.
6
+
7
+ **Repository**: [https://gitlab.gzl10.com/oss/nexus-backend](https://gitlab.gzl10.com/oss/nexus-backend)
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pnpm add nexus-backend
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript
18
+ import { start, stop, nexusEvents } from 'nexus-backend'
19
+
20
+ // Start server
21
+ await start({
22
+ port: 3000,
23
+ jwt: { secret: 'your-secret-at-least-32-characters' },
24
+ database: { url: 'file:./data.db' }
25
+ })
26
+
27
+ // Listen to CRUD events
28
+ nexusEvents.on('db.users.created', ({ data }) => {
29
+ console.log('User created:', data)
30
+ })
31
+
32
+ // Stop server
33
+ await stop()
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ | Option | Description | Default |
39
+ | -------------------- | ------------------------ | ----------------------- |
40
+ | `port` | Server port | `3000` |
41
+ | `host` | Server host | `0.0.0.0` |
42
+ | `database.url` | Database URL | `file:./dev.db` |
43
+ | `jwt.secret` | JWT secret (min 32 chars)| Required |
44
+ | `jwt.accessExpires` | Access token expiration | `15m` |
45
+ | `jwt.refreshExpires` | Refresh token expiration | `7d` |
46
+ | `cors.origin` | Allowed CORS origin | `http://localhost:3001` |
47
+
48
+ ### Supported Databases
49
+
50
+ - **SQLite**: `file:./data.db`
51
+ - **PostgreSQL**: `postgresql://user:pass@host:5432/db`
52
+ - **MySQL**: `mysql://user:pass@host:3306/db`
53
+
54
+ ## API Endpoints
55
+
56
+ - `POST /api/v1/auth/login` - Login
57
+ - `POST /api/v1/auth/register` - Register
58
+ - `POST /api/v1/auth/refresh` - Refresh token
59
+ - `GET /api/v1/users` - List users
60
+ - `GET /api/v1/posts` - List posts
61
+ - `GET /api/health` - Health check
62
+
63
+ ## Development
64
+
65
+ ```bash
66
+ pnpm dev # Server with hot reload
67
+ pnpm build # Production build
68
+ pnpm typecheck # Type check
69
+ pnpm lint # Linting
70
+ pnpm db:migrate # Run migrations
71
+ pnpm db:seed # Initial seed
72
+ ```
73
+
74
+ ## Support
75
+
76
+ <a href="https://www.buymeacoffee.com/gzl10g" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
77
+
78
+ ## License
79
+
80
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { existsSync } from "fs";
5
+ import { Command } from "commander";
6
+ import { consola } from "consola";
7
+ if (existsSync(".env")) {
8
+ process.loadEnvFile(".env");
9
+ }
10
+ var program = new Command();
11
+ program.name("nexus").description("Nexus Backend CLI").version("0.1.0");
12
+ program.command("ui").description("Open UI in browser").option("-p, --port <port>", "Nexus backend port").action(async (options) => {
13
+ const port = parseInt(options.port || process.env["PORT"] || "3000", 10);
14
+ const baseUrl = (process.env["BACKEND_URL"] || `http://localhost:${port}`).replace(/\/$/, "");
15
+ const url = `${baseUrl}/ui`;
16
+ try {
17
+ const res = await fetch(`${baseUrl}/health`);
18
+ if (!res.ok) throw new Error();
19
+ } catch {
20
+ consola.error(`Nexus no est\xE1 corriendo en ${baseUrl}`);
21
+ consola.info("Inicia el servidor primero con: pnpm dev");
22
+ process.exit(1);
23
+ }
24
+ const { exec } = await import("child_process");
25
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
26
+ exec(`${cmd} ${url}`);
27
+ consola.success(`Abriendo: ${url}`);
28
+ });
29
+ program.parse();
30
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { existsSync } from 'fs'\nimport { Command } from 'commander'\nimport { consola } from 'consola'\n\n// Cargar .env si existe (Node 20+)\nif (existsSync('.env')) {\n process.loadEnvFile('.env')\n}\n\nconst program = new Command()\n\nprogram\n .name('nexus')\n .description('Nexus Backend CLI')\n .version('0.1.0')\n\nprogram\n .command('ui')\n .description('Open UI in browser')\n .option('-p, --port <port>', 'Nexus backend port')\n .action(async (options) => {\n const port = parseInt(options.port || process.env['PORT'] || '3000', 10)\n const baseUrl = (process.env['BACKEND_URL'] || `http://localhost:${port}`).replace(/\\/$/, '')\n const url = `${baseUrl}/ui`\n\n // Verificar que Nexus está corriendo\n try {\n const res = await fetch(`${baseUrl}/health`)\n if (!res.ok) throw new Error()\n } catch {\n consola.error(`Nexus no está corriendo en ${baseUrl}`)\n consola.info('Inicia el servidor primero con: pnpm dev')\n process.exit(1)\n }\n\n // Abrir navegador\n const { exec } = await import('child_process')\n const cmd = process.platform === 'darwin' ? 'open'\n : process.platform === 'win32' ? 'start'\n : 'xdg-open'\n\n exec(`${cmd} ${url}`)\n consola.success(`Abriendo: ${url}`)\n })\n\nprogram.parse()\n"],"mappings":";;;AACA,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,eAAe;AAGxB,IAAI,WAAW,MAAM,GAAG;AACtB,UAAQ,YAAY,MAAM;AAC5B;AAEA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,OAAO,EACZ,YAAY,mBAAmB,EAC/B,QAAQ,OAAO;AAElB,QACG,QAAQ,IAAI,EACZ,YAAY,oBAAoB,EAChC,OAAO,qBAAqB,oBAAoB,EAChD,OAAO,OAAO,YAAY;AACzB,QAAM,OAAO,SAAS,QAAQ,QAAQ,QAAQ,IAAI,MAAM,KAAK,QAAQ,EAAE;AACvE,QAAM,WAAW,QAAQ,IAAI,aAAa,KAAK,oBAAoB,IAAI,IAAI,QAAQ,OAAO,EAAE;AAC5F,QAAM,MAAM,GAAG,OAAO;AAGtB,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,OAAO,SAAS;AAC3C,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM;AAAA,EAC/B,QAAQ;AACN,YAAQ,MAAM,iCAA8B,OAAO,EAAE;AACrD,YAAQ,KAAK,0CAA0C;AACvD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,EAAE,KAAK,IAAI,MAAM,OAAO,eAAe;AAC7C,QAAM,MAAM,QAAQ,aAAa,WAAW,SAChC,QAAQ,aAAa,UAAU,UAC/B;AAEZ,OAAK,GAAG,GAAG,IAAI,GAAG,EAAE;AACpB,UAAQ,QAAQ,aAAa,GAAG,EAAE;AACpC,CAAC;AAEH,QAAQ,MAAM;","names":[]}
@@ -0,0 +1,561 @@
1
+ import http from 'node:http';
2
+ import * as express_serve_static_core from 'express-serve-static-core';
3
+ import knex, { Knex } from 'knex';
4
+ import * as _casl_ability from '@casl/ability';
5
+ import { MongoAbility, RawRuleOf } from '@casl/ability';
6
+ import { Router, RequestHandler, Request } from 'express';
7
+ export { CookieOptions, NextFunction, Request, RequestHandler, Response, Router } from 'express';
8
+ import { Logger } from 'pino';
9
+ import { ZodSchema } from 'zod';
10
+ import pkg from 'eventemitter2';
11
+
12
+ interface SmtpConfig {
13
+ host: string;
14
+ port: number;
15
+ secure: boolean;
16
+ auth?: {
17
+ user: string;
18
+ pass: string;
19
+ };
20
+ from: string;
21
+ }
22
+ interface NexusConfig {
23
+ port?: number;
24
+ host?: string;
25
+ homePath?: string;
26
+ cors?: {
27
+ origin?: string | string[];
28
+ };
29
+ database?: {
30
+ url?: string;
31
+ };
32
+ jwt?: {
33
+ secret?: string;
34
+ accessExpires?: string;
35
+ refreshExpires?: string;
36
+ };
37
+ admin?: {
38
+ email?: string;
39
+ password?: string;
40
+ };
41
+ smtp?: Partial<SmtpConfig>;
42
+ }
43
+ interface ResolvedConfig {
44
+ nodeEnv: 'development' | 'production' | 'test';
45
+ port: number;
46
+ host: string;
47
+ homePath: string;
48
+ corsOrigin: string;
49
+ databaseUrl: string;
50
+ jwtSecret: string;
51
+ jwtAccessExpires: string;
52
+ jwtRefreshExpires: string;
53
+ adminEmail?: string;
54
+ adminPassword?: string;
55
+ smtp: SmtpConfig;
56
+ }
57
+
58
+ declare function start(config?: NexusConfig): Promise<http.Server>;
59
+ declare function stop(): Promise<void>;
60
+ declare function restart(config?: NexusConfig): Promise<http.Server>;
61
+ declare function isRunning(): boolean;
62
+
63
+ declare function createApp(): express_serve_static_core.Express;
64
+
65
+ declare const db: knex.Knex<any, any[]>;
66
+ declare function getDatabaseType(): 'sqlite' | 'postgresql' | 'mysql';
67
+ declare function destroyDb(): Promise<void>;
68
+
69
+ declare function getConfig(): ResolvedConfig;
70
+
71
+ interface MailAction {
72
+ label: string;
73
+ url: string;
74
+ }
75
+ interface SendMailOptions {
76
+ to: string | string[];
77
+ subject: string;
78
+ title?: string;
79
+ message?: string;
80
+ actions?: MailAction[];
81
+ html?: string;
82
+ text?: string;
83
+ from?: string;
84
+ replyTo?: string;
85
+ logoUrl?: string;
86
+ attachments?: Array<{
87
+ filename: string;
88
+ content: Buffer | string;
89
+ contentType?: string;
90
+ }>;
91
+ }
92
+ interface SendMailResult {
93
+ messageId: string;
94
+ accepted: string[];
95
+ rejected: string[];
96
+ }
97
+ declare class MailService {
98
+ private transporter;
99
+ private defaultFrom;
100
+ private defaultLogoUrl;
101
+ private logger;
102
+ private template;
103
+ constructor(config: SmtpConfig, logger: Logger);
104
+ send(options: SendMailOptions): Promise<SendMailResult | null>;
105
+ private renderTemplate;
106
+ private processConditionalBlock;
107
+ verify(): Promise<boolean>;
108
+ }
109
+
110
+ declare class AppError extends Error {
111
+ readonly statusCode: number;
112
+ constructor(message: string, statusCode?: number);
113
+ }
114
+ declare class NotFoundError extends AppError {
115
+ constructor(resource?: string);
116
+ }
117
+ declare class UnauthorizedError extends AppError {
118
+ constructor(message?: string);
119
+ }
120
+ declare class ForbiddenError extends AppError {
121
+ constructor(message?: string);
122
+ }
123
+ declare class ConflictError extends AppError {
124
+ constructor(message?: string);
125
+ }
126
+
127
+ interface Role {
128
+ id: string;
129
+ name: string;
130
+ description: string | null;
131
+ is_system: boolean;
132
+ created_at: Date;
133
+ updated_at: Date;
134
+ created_by: string | null;
135
+ updated_by: string | null;
136
+ }
137
+ interface RolePermission {
138
+ id: string;
139
+ role_id: string;
140
+ action: string;
141
+ subject: string;
142
+ conditions: Record<string, unknown> | null;
143
+ fields: string[] | null;
144
+ inverted: boolean;
145
+ created_at: Date;
146
+ updated_at: Date;
147
+ created_by: string | null;
148
+ updated_by: string | null;
149
+ }
150
+ /** Role con permisos incluidos */
151
+ interface RoleWithPermissions extends Role {
152
+ permissions: RolePermission[];
153
+ }
154
+ /** Role con conteo de usuarios */
155
+ interface RoleWithCounts extends Role {
156
+ permissions_count: number;
157
+ users_count: number;
158
+ }
159
+
160
+ interface User {
161
+ id: string;
162
+ email: string;
163
+ password: string;
164
+ name: string;
165
+ role_id: string;
166
+ metadata: Record<string, unknown> | null;
167
+ created_at: Date;
168
+ updated_at: Date;
169
+ created_by: string | null;
170
+ updated_by: string | null;
171
+ }
172
+ /** User sin password para respuestas API */
173
+ type UserWithoutPassword = Omit<User, 'password'>;
174
+ /** User con rol incluido */
175
+ interface UserWithRole extends UserWithoutPassword {
176
+ role: Role;
177
+ }
178
+
179
+ /**
180
+ * Registro de subjects CASL.
181
+ * Los módulos core definen sus subjects aquí.
182
+ * Los plugins pueden extender via declaration merging:
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * // my-plugin/src/types/abilities.d.ts
187
+ * declare module '@g10/nexus-backend' {
188
+ * interface SubjectRegistry {
189
+ * MyCustomEntity: true
190
+ * }
191
+ * }
192
+ * ```
193
+ */
194
+ interface SubjectRegistry {
195
+ UsrUser: true;
196
+ RolRole: true;
197
+ RolRolePermission: true;
198
+ all: true;
199
+ }
200
+ /** Subjects como strings (derivado del registro, extensible) */
201
+ type SubjectStrings = keyof SubjectRegistry;
202
+ type Actions = 'manage' | 'create' | 'read' | 'update' | 'delete';
203
+ /** Subjects válidos: strings del registro + instancias de objetos */
204
+ type Subjects = SubjectStrings | User | UserWithoutPassword | Role;
205
+ type AppAbility = MongoAbility<[Actions, Subjects]>;
206
+
207
+ declare const EventEmitter2: typeof pkg.EventEmitter2;
208
+ interface DbEventPayload {
209
+ table: string;
210
+ action: 'created' | 'updated' | 'deleted';
211
+ data: unknown;
212
+ timestamp: Date;
213
+ }
214
+ interface NexusEvents {
215
+ 'server.starting': {
216
+ port: number;
217
+ host: string;
218
+ };
219
+ 'server.started': {
220
+ port: number;
221
+ host: string;
222
+ };
223
+ 'server.stopping': undefined;
224
+ 'server.stopped': undefined;
225
+ 'server.restarting': undefined;
226
+ 'db.connected': {
227
+ type: 'sqlite' | 'postgresql' | 'mysql';
228
+ };
229
+ 'db.disconnected': undefined;
230
+ [key: `db.${string}.created`]: DbEventPayload;
231
+ [key: `db.${string}.updated`]: DbEventPayload;
232
+ [key: `db.${string}.deleted`]: DbEventPayload;
233
+ 'auth.login': {
234
+ userId: string;
235
+ email: string;
236
+ };
237
+ 'auth.logout': {
238
+ userId: string;
239
+ };
240
+ 'auth.refresh': {
241
+ userId: string;
242
+ };
243
+ 'auth.failed': {
244
+ email: string;
245
+ reason: string;
246
+ };
247
+ }
248
+ type NexusEventName = keyof NexusEvents;
249
+ type NexusEventPayload<T extends NexusEventName> = NexusEvents[T];
250
+ declare class TypedEventEmitter extends EventEmitter2 {
251
+ emitEvent<T extends NexusEventName>(event: T, ...args: NexusEvents[T] extends undefined ? [] : [NexusEvents[T]]): boolean;
252
+ onEvent<T extends NexusEventName>(event: T, listener: NexusEvents[T] extends undefined ? () => void : (payload: NexusEvents[T]) => void): this;
253
+ onceEvent<T extends NexusEventName>(event: T, listener: NexusEvents[T] extends undefined ? () => void : (payload: NexusEvents[T]) => void): this;
254
+ offEvent<T extends NexusEventName>(event: T, listener: NexusEvents[T] extends undefined ? () => void : (payload: NexusEvents[T]) => void): this;
255
+ }
256
+ declare const nexusEvents: TypedEventEmitter;
257
+
258
+ /**
259
+ * Request autenticado con usuario y abilities CASL
260
+ * Los módulos usan este tipo en lugar de importar de ../../types/
261
+ */
262
+ interface AuthRequest extends Request {
263
+ user: User;
264
+ ability: AppAbility;
265
+ }
266
+
267
+ /**
268
+ * Helpers para migraciones de base de datos
269
+ */
270
+ interface MigrationHelpers {
271
+ /** Añade created_at y updated_at */
272
+ addTimestamps: (table: Knex.CreateTableBuilder, db: Knex) => void;
273
+ /** Añade created_by y updated_by si no existen */
274
+ addAuditFieldsIfMissing: (db: Knex, tableName: string) => Promise<void>;
275
+ /** Añade columna si no existe (idempotente). Retorna true si se añadió */
276
+ addColumnIfMissing: (db: Knex, tableName: string, columnName: string, columnBuilder: (table: Knex.AlterTableBuilder) => void) => Promise<boolean>;
277
+ }
278
+ /**
279
+ * Opciones para el middleware de validación Zod
280
+ */
281
+ interface ValidateOptions {
282
+ body?: ZodSchema;
283
+ query?: ZodSchema;
284
+ params?: ZodSchema;
285
+ }
286
+ /**
287
+ * Middlewares disponibles en el contexto
288
+ * Incluye validate (genérico) y middlewares registrados por módulos
289
+ */
290
+ interface ModuleMiddlewares {
291
+ /** Validación con Zod */
292
+ validate: (schemas: ValidateOptions) => RequestHandler;
293
+ /** Middlewares registrados dinámicamente por módulos */
294
+ [key: string]: RequestHandler | ((...args: any[]) => RequestHandler) | undefined;
295
+ }
296
+ /**
297
+ * Errores disponibles en el contexto
298
+ * Los módulos usan ctx.errors en lugar de importar directamente
299
+ */
300
+ interface ModuleErrors {
301
+ AppError: typeof AppError;
302
+ NotFoundError: typeof NotFoundError;
303
+ UnauthorizedError: typeof UnauthorizedError;
304
+ ForbiddenError: typeof ForbiddenError;
305
+ ConflictError: typeof ConflictError;
306
+ }
307
+ /**
308
+ * Funciones de abilities CASL disponibles en el contexto
309
+ */
310
+ interface ModuleAbilities {
311
+ /** Construye abilities desde permisos de BD */
312
+ defineAbilityFor: (user: User, permissions: RolePermission[]) => AppAbility;
313
+ /** Serializa abilities para enviar al frontend */
314
+ packRules: (ability: AppAbility) => RawRuleOf<AppAbility>[];
315
+ /** Wrapper para verificar permisos contra instancias */
316
+ subject: typeof _casl_ability.subject;
317
+ /** Error de CASL para throwUnlessCan */
318
+ ForbiddenError: typeof _casl_ability.ForbiddenError;
319
+ }
320
+ /**
321
+ * Contexto inyectado a los módulos
322
+ * Contiene todas las herramientas necesarias para operar
323
+ */
324
+ interface ModuleContext {
325
+ /** Conexión a base de datos */
326
+ db: Knex;
327
+ /** Logger */
328
+ logger: Logger;
329
+ /** Generar ID único */
330
+ generateId: () => string;
331
+ /** Tipo de base de datos */
332
+ dbType: 'sqlite' | 'postgresql' | 'mysql';
333
+ /** Helpers para migraciones */
334
+ helpers: MigrationHelpers;
335
+ /** Crear un router Express */
336
+ createRouter: () => Router;
337
+ /** Middlewares disponibles (validate + registrados por módulos) */
338
+ middleware: ModuleMiddlewares;
339
+ /** Registrar middleware para uso de otros módulos */
340
+ registerMiddleware: (name: string, handler: RequestHandler) => void;
341
+ /** Configuración resuelta de la aplicación */
342
+ config: ResolvedConfig;
343
+ /** Errores para lanzar en módulos (evita imports directos) */
344
+ errors: ModuleErrors;
345
+ /** Funciones de abilities CASL (evita imports directos) */
346
+ abilities: ModuleAbilities;
347
+ /** Event emitter para comunicación entre módulos */
348
+ events: TypedEventEmitter;
349
+ /** Servicio de email SMTP */
350
+ mail: MailService;
351
+ }
352
+ /**
353
+ * Requisitos para activar un módulo
354
+ */
355
+ interface ModuleRequirements {
356
+ /** Variables de entorno requeridas */
357
+ env?: string[];
358
+ /** Módulos que deben estar activos */
359
+ modules?: string[];
360
+ }
361
+ /**
362
+ * Tipo de campo para formularios dinámicos
363
+ */
364
+ type FormFieldType = 'text' | 'email' | 'password' | 'number' | 'textarea' | 'markdown' | 'select' | 'checkbox' | 'date' | 'datetime';
365
+ /**
366
+ * JSON Schema para validación de campos (subset serializable)
367
+ * @see https://json-schema.org/understanding-json-schema/
368
+ */
369
+ interface FieldValidation {
370
+ /** Tipo JSON Schema */
371
+ type?: 'string' | 'number' | 'integer' | 'boolean';
372
+ /** Longitud mínima (strings) o valor mínimo (numbers) */
373
+ minLength?: number;
374
+ maxLength?: number;
375
+ minimum?: number;
376
+ maximum?: number;
377
+ /** Regex pattern para strings */
378
+ pattern?: string;
379
+ /** Formatos predefinidos: email, uri, date, date-time, uuid */
380
+ format?: 'email' | 'uri' | 'date' | 'date-time' | 'uuid';
381
+ /** Mensaje de error personalizado */
382
+ errorMessage?: string;
383
+ }
384
+ /**
385
+ * Configuración de un campo de formulario
386
+ */
387
+ interface FormField {
388
+ /** Etiqueta del campo */
389
+ label: string;
390
+ /** Tipo de input */
391
+ type: FormFieldType;
392
+ /** Campo requerido (default: false) */
393
+ required?: boolean;
394
+ /** Placeholder del input */
395
+ placeholder?: string;
396
+ /** Solo mostrar en creación, no en edición (ej: password) */
397
+ createOnly?: boolean;
398
+ /** Campo deshabilitado */
399
+ disabled?: boolean;
400
+ /** Campo oculto */
401
+ hidden?: boolean;
402
+ /** Endpoint para cargar opciones (para type: 'select') */
403
+ optionsEndpoint?: string;
404
+ /** Campo a usar como value en opciones (default: 'id') */
405
+ optionValue?: string;
406
+ /** Campo a usar como label en opciones (default: 'name') */
407
+ optionLabel?: string;
408
+ /** Validación JSON Schema (serializable) */
409
+ validation?: FieldValidation;
410
+ }
411
+ /**
412
+ * Tipo de visualización de lista
413
+ */
414
+ type ListType = 'table' | 'list' | 'grid' | 'masonry';
415
+ /**
416
+ * Configuración de una entidad/tabla del módulo para UI CRUD
417
+ */
418
+ interface ModuleEntity {
419
+ /** Nombre del subject CASL (debe coincidir con la tabla) */
420
+ name: string;
421
+ /** Nombre para mostrar en UI */
422
+ label: string;
423
+ /** Campos a mostrar en la tabla: { field: 'Label' } */
424
+ listFields: Record<string, string>;
425
+ /** Campos del formulario: { field: FormField } */
426
+ formFields?: Record<string, FormField>;
427
+ /** Campo usado como título/label en la UI */
428
+ labelField: string;
429
+ /** Prefijo de ruta si es diferente al del módulo */
430
+ routePrefix?: string;
431
+ /** Modo de edición: 'modal' (default) o 'page' para formularios grandes */
432
+ editMode?: 'modal' | 'page';
433
+ /** Tipo de visualización: 'table' (default), 'list', 'grid', 'masonry' */
434
+ listType?: ListType;
435
+ }
436
+ /**
437
+ * Manifest de un módulo Nexus
438
+ *
439
+ * Define la estructura y capacidades de un módulo para auto-discovery.
440
+ * Usado por el sistema de plugins y extensiones.
441
+ */
442
+ interface ModuleManifest {
443
+ /** Identificador único del módulo (ej: 'users', 'posts') */
444
+ name: string;
445
+ /** Código único del módulo (ej: 'USR', 'PST') - para referencias cortas */
446
+ code: string;
447
+ /** Nombre para mostrar en UI */
448
+ label: string;
449
+ /** Icono del módulo (nombre de @vicons/ionicons5 o ruta a png/svg) */
450
+ icon?: string;
451
+ /** Descripción del módulo */
452
+ description?: string;
453
+ /** Versión del módulo (semver) */
454
+ version?: string;
455
+ /** Tipo de módulo: 'core' | 'plugin' | 'auth-plugin' | 'custom' */
456
+ type?: 'core' | 'plugin' | 'auth-plugin' | 'custom';
457
+ /** Categoría del módulo para agrupar en sidebar (vacío = raíz) */
458
+ category?: string;
459
+ /** Dependencias de otros módulos (para orden de migración/rutas) */
460
+ dependencies?: string[];
461
+ /** Requisitos para activar el módulo */
462
+ required?: ModuleRequirements;
463
+ /** Función de migración de base de datos */
464
+ migrate?: (ctx: ModuleContext) => Promise<void>;
465
+ /** Función de seed de datos iniciales */
466
+ seed?: (ctx: ModuleContext) => Promise<void>;
467
+ /** Inicialización del módulo (registrar middlewares, etc.) - se ejecuta antes de routes */
468
+ init?: (ctx: ModuleContext) => void;
469
+ /** Factory de rutas del módulo (recibe contexto) */
470
+ routes?: (ctx: ModuleContext) => Router;
471
+ /** Prefijo de rutas (default: /{name}) */
472
+ routePrefix?: string;
473
+ /** Subjects CASL que expone el módulo */
474
+ subjects?: string[];
475
+ /** Entidades/tablas del módulo con config CRUD para UI */
476
+ entities?: ModuleEntity[];
477
+ }
478
+ /**
479
+ * Manifest de un plugin Nexus
480
+ *
481
+ * Agrupa múltiples módulos relacionados en un solo paquete instalable.
482
+ * Los plugins se distribuyen como paquetes npm con peerDependency en @gzl10/nexus-backend.
483
+ */
484
+ interface PluginManifest {
485
+ /** Nombre del plugin (normalmente el nombre del paquete npm) */
486
+ name: string;
487
+ /** Versión del plugin (semver) */
488
+ version: string;
489
+ /** Descripción del plugin */
490
+ description: string;
491
+ /** Módulos incluidos en el plugin */
492
+ modules: ModuleManifest[];
493
+ }
494
+
495
+ /**
496
+ * Ordenar módulos por dependencias (topological sort)
497
+ * Garantiza que un módulo se procese después de sus dependencias
498
+ */
499
+ declare function getOrderedModules(): ModuleManifest[];
500
+ /**
501
+ * Registrar módulo dinámicamente (para plugins)
502
+ */
503
+ declare function registerModule(mod: ModuleManifest): void;
504
+ /**
505
+ * Obtener todos los módulos registrados
506
+ */
507
+ declare function getModules(): ModuleManifest[];
508
+ /**
509
+ * Obtener módulo por nombre
510
+ */
511
+ declare function getModule(name: string): ModuleManifest | undefined;
512
+ /**
513
+ * Obtener todos los subjects registrados en módulos
514
+ * Incluye 'all' que siempre está disponible
515
+ */
516
+ declare function getRegisteredSubjects(): string[];
517
+ /**
518
+ * Validar que un subject existe en algún módulo registrado
519
+ */
520
+ declare function isValidSubject(subject: string): boolean;
521
+
522
+ interface PaginationParams {
523
+ page: number;
524
+ limit: number;
525
+ }
526
+ interface PaginatedResult<T> {
527
+ items: T[];
528
+ total: number;
529
+ page: number;
530
+ limit: number;
531
+ totalPages: number;
532
+ hasNext: boolean;
533
+ }
534
+
535
+ interface RefreshToken {
536
+ id: string;
537
+ token: string;
538
+ user_id: string;
539
+ expires_at: Date;
540
+ created_at: Date;
541
+ }
542
+ interface JwtPayload {
543
+ userId: string;
544
+ email: string;
545
+ roleId: string;
546
+ }
547
+ interface TokenPair {
548
+ accessToken: string;
549
+ refreshToken: string;
550
+ }
551
+
552
+ /**
553
+ * Construye abilities CASL desde permisos de BD.
554
+ * @param user - Usuario autenticado
555
+ * @param permissions - Permisos del rol del usuario (cargados desde BD)
556
+ */
557
+ declare function defineAbilityFor(user: User, permissions: RolePermission[]): AppAbility;
558
+ declare function packRules(ability: AppAbility): RawRuleOf<AppAbility>[];
559
+ declare function unpackRules(rules: RawRuleOf<AppAbility>[]): AppAbility;
560
+
561
+ export { type Actions, type AppAbility, type AuthRequest, type DbEventPayload, type JwtPayload, type MigrationHelpers, type ModuleAbilities, type ModuleContext, type ModuleEntity, type ModuleErrors, type ModuleManifest, type ModuleMiddlewares, type ModuleRequirements, type NexusConfig, type NexusEventName, type NexusEventPayload, type NexusEvents, type PaginatedResult, type PaginationParams, type PluginManifest, type RefreshToken, type ResolvedConfig, type Role, type RolePermission, type RoleWithCounts, type RoleWithPermissions, type SubjectRegistry, type SubjectStrings, type Subjects, type TokenPair, type User, type UserWithRole, type UserWithoutPassword, type ValidateOptions, createApp, db, defineAbilityFor, destroyDb, getConfig, getDatabaseType, getModule, getModules, getOrderedModules, getRegisteredSubjects, isRunning, isValidSubject, nexusEvents, packRules, registerModule, restart, start, stop, unpackRules };