@gzl10/nexus-sdk 0.11.1 → 0.12.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +163 -31
  2. package/package.json +5 -13
  3. package/README.md +0 -321
package/dist/index.d.ts CHANGED
@@ -114,7 +114,7 @@ type DbType = 'string' | 'text' | 'integer' | 'decimal' | 'boolean' | 'date' | '
114
114
  /**
115
115
  * Tipos de input en UI
116
116
  */
117
- type InputType = 'text' | 'email' | 'password' | 'url' | 'tel' | 'number' | 'decimal' | 'textarea' | 'markdown' | 'select' | 'multiselect' | 'tags' | 'checkbox' | 'switch' | 'date' | 'datetime' | 'file' | 'fileArray' | 'image' | 'imageArray' | 'hidden';
117
+ type InputType = 'text' | 'email' | 'password' | 'url' | 'tel' | 'number' | 'decimal' | 'textarea' | 'markdown' | 'json' | 'select' | 'multiselect' | 'tags' | 'checkbox' | 'switch' | 'date' | 'datetime' | 'file' | 'fileArray' | 'image' | 'imageArray' | 'hidden';
118
118
  /**
119
119
  * Configuración de base de datos para un campo
120
120
  */
@@ -159,6 +159,7 @@ interface FieldOptions {
159
159
  value: string;
160
160
  label: string;
161
161
  }>;
162
+ allowCreate?: boolean;
162
163
  }
163
164
  /**
164
165
  * Restricciones de acceso CASL para un campo
@@ -214,7 +215,7 @@ interface FieldDefinition {
214
215
  hint?: string;
215
216
  hidden?: boolean;
216
217
  disabled?: boolean;
217
- db: FieldDbConfig;
218
+ db?: FieldDbConfig;
218
219
  relation?: FieldRelation;
219
220
  validation?: FieldValidationConfig;
220
221
  options?: FieldOptions;
@@ -279,6 +280,63 @@ interface EntityCaslConfig {
279
280
  */
280
281
  sensitiveFields?: string[];
281
282
  }
283
+ /**
284
+ * Action vinculada a un registro de la entidad
285
+ * Define operaciones que actúan sobre registros existentes
286
+ *
287
+ * Ruta generada: GET|POST /{entityRoutePrefix}/{key}/:id
288
+ *
289
+ * @example
290
+ * ```typescript
291
+ * actions: [{
292
+ * key: 'download',
293
+ * label: 'Download File',
294
+ * icon: 'mdi:download',
295
+ * method: 'GET',
296
+ * select: ['id', 'filename', 'mimetype'],
297
+ * handler: async (ctx, input, _req, res) => {
298
+ * const { _record: file, _authUserId } = input
299
+ * // file y _authUserId vienen inyectados automáticamente
300
+ * }
301
+ * }]
302
+ * ```
303
+ */
304
+ interface EntityAction {
305
+ /** Key única para la ruta (ej: 'download' → /download/:id) */
306
+ key: string;
307
+ /** Label para UI */
308
+ label: string;
309
+ /** Icono Iconify (default: 'mdi:play') */
310
+ icon?: string;
311
+ /** Método HTTP (default: 'POST') */
312
+ method?: 'GET' | 'POST';
313
+ /** Campos a seleccionar del registro (default: todos) */
314
+ select?: string[];
315
+ /** Schema de validación del body (opcional) */
316
+ inputSchema?: ValidationSchema;
317
+ /** Schema del output (documentación) */
318
+ outputSchema?: ValidationSchema;
319
+ /** Middleware custom (ej: multer) */
320
+ middleware?: (ctx: ModuleContext) => RequestHandler | RequestHandler[];
321
+ /**
322
+ * Handler - recibe _record y _authUserId automáticamente
323
+ * @param ctx - Contexto del módulo
324
+ * @param input - Body validado + _record + _authUserId
325
+ * @param req - Express Request
326
+ * @param res - Express Response (para streams, headers custom)
327
+ */
328
+ handler: (ctx: ModuleContext, input: unknown, req?: Request, res?: Response) => Promise<unknown>;
329
+ /**
330
+ * Permisos CASL específicos para esta action
331
+ * Si no se define, hereda los permisos de la entidad padre
332
+ */
333
+ casl?: {
334
+ /** Action CASL (default: 'execute') */
335
+ action?: string;
336
+ /** Conditions adicionales */
337
+ conditions?: Record<string, unknown>;
338
+ };
339
+ }
282
340
  /**
283
341
  * Propiedades base compartidas por todas las EntityDefinition
284
342
  */
@@ -293,6 +351,8 @@ interface BaseEntityDefinition {
293
351
  casl?: EntityCaslConfig;
294
352
  /** Prefijo de ruta para montar (default: inferido de table) */
295
353
  routePrefix?: string;
354
+ /** Orden de visualización en UI (menor = primero, default: 999) */
355
+ order?: number;
296
356
  }
297
357
  /**
298
358
  * Entidad de colección - CRUD completo (users, posts, orders)
@@ -310,6 +370,8 @@ interface CollectionEntityDefinition extends BaseEntityDefinition {
310
370
  indexes?: EntityIndex[];
311
371
  /** Usar deleted_at en vez de DELETE físico */
312
372
  softDelete?: boolean;
373
+ /** Actions vinculadas a registros de esta entidad */
374
+ actions?: EntityAction[];
313
375
  }
314
376
  /**
315
377
  * Entidad singleton - Un solo registro en tabla compartida sys_settings
@@ -334,6 +396,8 @@ interface SingleEntityDefinition {
334
396
  casl?: EntityCaslConfig;
335
397
  /** Prefijo de ruta para montar (default: inferido de key) */
336
398
  routePrefix?: string;
399
+ /** Actions vinculadas a esta entidad single */
400
+ actions?: EntityAction[];
337
401
  }
338
402
  /**
339
403
  * Entidad de referencia - Catálogos con CRUD admin (countries, currencies)
@@ -370,23 +434,43 @@ interface EventEntityDefinition extends BaseEntityDefinition {
370
434
  }
371
435
  /**
372
436
  * Entidad de acción - Comandos/operaciones sin persistencia
437
+ * Para actions que operan sobre registros existentes, usar EntityAction en la entidad padre
373
438
  */
374
439
  interface ActionEntityDefinition {
375
440
  type: 'action';
376
441
  /** Nombre para mostrar en UI */
377
442
  label: string;
443
+ /** Icono Iconify para UI (default: 'mdi:play') */
444
+ icon?: string;
378
445
  /** Solo campos de formulario para input, sin BD */
379
446
  fields: Record<string, FieldDefinition>;
380
447
  /** Schema Zod de input (se valida antes de ejecutar) */
381
448
  inputSchema?: ValidationSchema;
382
449
  /** Schema Zod de output (documenta respuesta) */
383
450
  outputSchema?: ValidationSchema;
384
- /** Handler que ejecuta la acción */
385
- handler?: (ctx: ModuleContext, input: unknown) => Promise<unknown>;
451
+ /** Método HTTP (default: POST) */
452
+ method?: 'GET' | 'POST';
453
+ /**
454
+ * Middleware a aplicar antes del handler (ej: multer para uploads)
455
+ * Puede retornar un middleware o un array de middlewares
456
+ */
457
+ middleware?: (ctx: ModuleContext) => RequestHandler | RequestHandler[];
458
+ /**
459
+ * Handler que ejecuta la acción
460
+ * @param ctx - Contexto del módulo
461
+ * @param input - Datos del body. Incluye _authUserId si hay usuario autenticado.
462
+ * @param req - Request de Express (opcional, para acceder a req.file, etc.)
463
+ * @param res - Response de Express (opcional, para streams, headers custom, etc.)
464
+ */
465
+ handler?: (ctx: ModuleContext, input: unknown, req?: Request, res?: Response) => Promise<unknown>;
466
+ /** HTTP status code para respuesta exitosa (default: 200, usar 201 para creación) */
467
+ successStatus?: number;
386
468
  /** Autorización CASL */
387
469
  casl?: EntityCaslConfig;
388
470
  /** Prefijo de ruta para montar */
389
471
  routePrefix?: string;
472
+ /** Orden de visualización en UI (menor = primero, default: 999) */
473
+ order?: number;
390
474
  }
391
475
  /**
392
476
  * Entidad externa - Datos de APIs externas (stripe_customers, github_repos)
@@ -422,6 +506,8 @@ interface ExternalEntityDefinition {
422
506
  casl?: EntityCaslConfig;
423
507
  /** Prefijo de ruta para montar */
424
508
  routePrefix?: string;
509
+ /** Orden de visualización en UI (menor = primero, default: 999) */
510
+ order?: number;
425
511
  }
426
512
  /**
427
513
  * Entidad virtual - Orquestación de múltiples fuentes (unified_customers)
@@ -443,6 +529,8 @@ interface VirtualEntityDefinition {
443
529
  casl?: EntityCaslConfig;
444
530
  /** Prefijo de ruta para montar */
445
531
  routePrefix?: string;
532
+ /** Orden de visualización en UI (menor = primero, default: 999) */
533
+ order?: number;
446
534
  }
447
535
  /**
448
536
  * Entidad computed - KPIs, estadísticas, métricas calculadas
@@ -469,6 +557,8 @@ interface ComputedEntityDefinition {
469
557
  casl?: EntityCaslConfig;
470
558
  /** Prefijo de ruta para montar */
471
559
  routePrefix?: string;
560
+ /** Orden de visualización en UI (menor = primero, default: 999) */
561
+ order?: number;
472
562
  }
473
563
  /**
474
564
  * Entidad view - Vista optimizada para lectura (projections, denormalizaciones)
@@ -492,6 +582,8 @@ interface ViewEntityDefinition {
492
582
  casl?: EntityCaslConfig;
493
583
  /** Prefijo de ruta para montar */
494
584
  routePrefix?: string;
585
+ /** Orden de visualización en UI (menor = primero, default: 999) */
586
+ order?: number;
495
587
  }
496
588
  /**
497
589
  * Entidad config - Configuración por módulo/tenant
@@ -600,9 +692,56 @@ interface ContextHelpers {
600
692
  generateId: () => string;
601
693
  addTimestamps: (table: Knex.CreateTableBuilder, db: Knex) => void;
602
694
  addAuditFieldsIfMissing: (db: Knex, tableName: string) => Promise<void>;
695
+ /** Añade campo is_default a tablas de tipo config */
696
+ addConfigDefaultField: (db: Knex, tableName: string) => Promise<void>;
603
697
  addColumnIfMissing: (db: Knex, tableName: string, columnName: string, columnBuilder: (table: Knex.AlterTableBuilder) => void) => Promise<boolean>;
604
698
  getLibPath: () => string;
605
699
  getProjectPath: () => string;
700
+ /** Timestamp formateado para el driver de BD actual (MySQL: 'YYYY-MM-DD HH:MM:SS', otros: ISO 8601) */
701
+ nowTimestamp: (db: Knex) => string;
702
+ /** Formatea una fecha para el driver de BD actual */
703
+ formatTimestamp: (db: Knex, date?: Date) => string;
704
+ }
705
+ /**
706
+ * Utilidades criptográficas del contexto
707
+ */
708
+ interface ContextCrypto {
709
+ /** Hash de password usando bcrypt (cost factor 12) */
710
+ hashPassword: (password: string) => Promise<string>;
711
+ /** Verifica password contra hash bcrypt (timing-safe) */
712
+ verifyPassword: (password: string, hash: string) => Promise<boolean>;
713
+ /** Hash dummy para timing-safe comparison cuando usuario no existe */
714
+ DUMMY_HASH: string;
715
+ }
716
+ /**
717
+ * API de Socket.IO del contexto
718
+ * @note Solo disponible si Socket.IO está inicializado
719
+ */
720
+ interface ContextSocket {
721
+ /** Obtiene la instancia de Socket.IO (throws si no inicializado) */
722
+ getIO: () => unknown;
723
+ /** Verifica si Socket.IO está inicializado */
724
+ isInitialized: () => boolean;
725
+ /** Verifica si un usuario específico está conectado */
726
+ isUserConnected: (userId: string) => boolean;
727
+ /** Obtiene el número de sockets conectados para un usuario */
728
+ getUserSocketCount: (userId: string) => number;
729
+ /** Obtiene los IDs de usuarios conectados */
730
+ getConnectedUsers: () => string[];
731
+ }
732
+ /**
733
+ * API del Engine para introspección de módulos y plugins
734
+ * Permite acceder al registro de módulos y plugins cargados
735
+ */
736
+ interface ContextEngine {
737
+ /** Obtiene todos los módulos registrados */
738
+ getModules: () => ModuleManifest[];
739
+ /** Obtiene todos los plugins registrados */
740
+ getPlugins: () => PluginManifest[];
741
+ /** Obtiene los subjects CASL de un módulo específico */
742
+ getModuleSubjects: (mod: ModuleManifest) => string[];
743
+ /** Obtiene todos los subjects CASL registrados en el sistema */
744
+ getRegisteredSubjects: () => string[];
606
745
  }
607
746
  /**
608
747
  * Schema de validación genérico (compatible con Zod sin depender de él)
@@ -780,31 +919,16 @@ interface ConfigEntityService<T = unknown> extends BaseEntityService<T> {
780
919
  set(key: string, value: unknown, userId?: string): Promise<T>;
781
920
  getAll(): Promise<Record<string, unknown>>;
782
921
  }
783
- /**
784
- * Servicio para entidades Reference (lectura con join automático)
785
- */
786
- interface ReferenceEntityService<T = unknown> extends BaseEntityService<T> {
787
- }
788
- /**
789
- * Servicio para entidades View (vista SQL, solo lectura)
790
- */
791
- interface ViewEntityService<T = unknown> extends BaseEntityService<T> {
792
- }
793
- /**
794
- * Servicio para entidades Computed (campos calculados, solo lectura)
795
- */
796
- interface ComputedEntityService<T = unknown> extends BaseEntityService<T> {
797
- }
798
- /**
799
- * Servicio para entidades External (datos de API externa)
800
- */
801
- interface ExternalEntityService<T = unknown> extends BaseEntityService<T> {
802
- }
803
- /**
804
- * Servicio para entidades Virtual (orquestación de múltiples fuentes)
805
- */
806
- interface VirtualEntityService<T = unknown> extends BaseEntityService<T> {
807
- }
922
+ /** Servicio para entidades Reference (lectura con join automático) */
923
+ type ReferenceEntityService<T = unknown> = BaseEntityService<T>;
924
+ /** Servicio para entidades View (vista SQL, solo lectura) */
925
+ type ViewEntityService<T = unknown> = BaseEntityService<T>;
926
+ /** Servicio para entidades Computed (campos calculados, solo lectura) */
927
+ type ComputedEntityService<T = unknown> = BaseEntityService<T>;
928
+ /** Servicio para entidades External (datos de API externa) */
929
+ type ExternalEntityService<T = unknown> = BaseEntityService<T>;
930
+ /** Servicio para entidades Virtual (orquestación de múltiples fuentes) */
931
+ type VirtualEntityService<T = unknown> = BaseEntityService<T>;
808
932
  /**
809
933
  * Servicio para entidades Action (ejecutar acciones)
810
934
  */
@@ -892,12 +1016,20 @@ interface ModuleContext {
892
1016
  db: Knex;
893
1017
  logger: Logger;
894
1018
  helpers: ContextHelpers;
1019
+ /** Utilidades criptográficas (hashPassword, verifyPassword) */
1020
+ crypto: ContextCrypto;
1021
+ /** API de Socket.IO para comunicación en tiempo real */
1022
+ socket: ContextSocket;
1023
+ /** API del Engine para introspección de módulos y plugins */
1024
+ engine: ContextEngine;
895
1025
  createRouter: () => Router;
896
1026
  middleware: ModuleMiddlewares;
897
1027
  /** Configuración resuelta de la aplicación */
898
1028
  config: Record<string, unknown>;
899
1029
  errors: {
900
- AppError: new (message: string, statusCode?: number) => Error;
1030
+ AppError: new (message: string, statusCode?: number, details?: unknown) => Error & {
1031
+ details?: unknown;
1032
+ };
901
1033
  NotFoundError: new (message?: string) => Error;
902
1034
  UnauthorizedError: new (message?: string) => Error;
903
1035
  ForbiddenError: new (message?: string) => Error;
@@ -997,4 +1129,4 @@ interface PluginManifest {
997
1129
  modules: ModuleManifest[];
998
1130
  }
999
1131
 
1000
- export { type AbilityLike, type ActionEntityDefinition, type ActionEntityService, type AuthRequest, type BaseEntityService, type BaseMailService, type BaseRolesService, type BaseUser, type BaseUsersService, CATEGORIES, CATEGORY_ORDER, type CaslAction, type Category, type CategoryMeta, type CollectionEntityDefinition, type CollectionEntityService, type ComputedEntityDefinition, type ComputedEntityService, type ConfigEntityDefinition, type ConfigEntityService, type ContextHelpers, type CoreServices, type CreateEntityServiceOptions, type DbType, type EntityCaslConfig, type EntityController, type EntityDefinition, type EntityHandler, type EntityIndex, type EntityQuery, type EntityServiceHooks, type EntityType, type EventEmitterLike, type EventEntityDefinition, type EventEntityService, type ExternalEntityDefinition, type ExternalEntityService, type FieldCaslAccess, type FieldDbConfig, type FieldDefinition, type FieldMeta, type FieldOptions, type FieldRelation, type FieldStorageConfig, type FieldValidationConfig, type ForbiddenErrorConstructor, type ForbiddenErrorInstance, type InputType, type KnexAlterTableBuilder, type KnexCreateTableBuilder, type KnexTransaction, type LoggerReporter, type ModuleAbilities, type ModuleContext, type ModuleManifest, type ModuleMiddlewares, type ModuleRequirements, type NonPersistentEntityDefinition, type OwnershipCondition, type PaginatedResult, type PaginationParams, type PersistentEntityDefinition, type PluginAuthRequest, type PluginManifest, type ReferenceEntityDefinition, type ReferenceEntityService, type RolePermission, type SendMailOptions, type SendMailResult, type SingleEntityDefinition, type SingleEntityService, type TempEntityDefinition, type TempEntityService, type ValidateSchemas, type ValidationSchema, type ViewEntityDefinition, type ViewEntityService, type VirtualEntityDefinition, type VirtualEntityService, getEntityName, getEntitySubject, hasTable, isPersistentEntity, isSingletonEntity };
1132
+ export { type AbilityLike, type ActionEntityDefinition, type ActionEntityService, type AuthRequest, type BaseEntityService, type BaseMailService, type BaseRolesService, type BaseUser, type BaseUsersService, CATEGORIES, CATEGORY_ORDER, type CaslAction, type Category, type CategoryMeta, type CollectionEntityDefinition, type CollectionEntityService, type ComputedEntityDefinition, type ComputedEntityService, type ConfigEntityDefinition, type ConfigEntityService, type ContextCrypto, type ContextEngine, type ContextHelpers, type ContextSocket, type CoreServices, type CreateEntityServiceOptions, type DbType, type EntityAction, type EntityCaslConfig, type EntityController, type EntityDefinition, type EntityHandler, type EntityIndex, type EntityQuery, type EntityServiceHooks, type EntityType, type EventEmitterLike, type EventEntityDefinition, type EventEntityService, type ExternalEntityDefinition, type ExternalEntityService, type FieldCaslAccess, type FieldDbConfig, type FieldDefinition, type FieldMeta, type FieldOptions, type FieldRelation, type FieldStorageConfig, type FieldValidationConfig, type ForbiddenErrorConstructor, type ForbiddenErrorInstance, type InputType, type KnexAlterTableBuilder, type KnexCreateTableBuilder, type KnexTransaction, type LoggerReporter, type ModuleAbilities, type ModuleContext, type ModuleManifest, type ModuleMiddlewares, type ModuleRequirements, type NonPersistentEntityDefinition, type OwnershipCondition, type PaginatedResult, type PaginationParams, type PersistentEntityDefinition, type PluginAuthRequest, type PluginManifest, type ReferenceEntityDefinition, type ReferenceEntityService, type RolePermission, type SendMailOptions, type SendMailResult, type SingleEntityDefinition, type SingleEntityService, type TempEntityDefinition, type TempEntityService, type ValidateSchemas, type ValidationSchema, type ViewEntityDefinition, type ViewEntityService, type VirtualEntityDefinition, type VirtualEntityService, getEntityName, getEntitySubject, hasTable, isPersistentEntity, isSingletonEntity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gzl10/nexus-sdk",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "SDK types for creating Nexus plugins and modules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -23,22 +23,12 @@
23
23
  ],
24
24
  "author": "Gonzalo Díez <gonzalo@gzl10.com>",
25
25
  "license": "MIT",
26
- "homepage": "https://www.gzl10.com",
27
- "repository": {
28
- "type": "git",
29
- "url": "https://gitlab.gzl10.com/oss/nexus-sdk"
30
- },
31
- "funding": {
32
- "type": "github",
33
- "url": "https://github.com/sponsors/gzl10"
34
- },
35
26
  "devDependencies": {
36
27
  "@types/express": "^5.0.2",
37
28
  "express": "^5.0.1",
38
29
  "knex": "^3.1.0",
39
30
  "pino": "^9.6.0",
40
- "tsup": "^8.4.0",
41
- "typescript": "^5.8.3"
31
+ "tsup": "^8.4.0"
42
32
  },
43
33
  "peerDependencies": {
44
34
  "express": "^5.0.1",
@@ -62,6 +52,8 @@
62
52
  },
63
53
  "scripts": {
64
54
  "build": "tsup src/index.ts --format esm --dts --clean",
65
- "typecheck": "tsc --noEmit"
55
+ "typecheck": "tsc --noEmit",
56
+ "lint": "eslint src",
57
+ "clean": "rm -rf dist node_modules"
66
58
  }
67
59
  }
package/README.md DELETED
@@ -1,321 +0,0 @@
1
- # @gzl10/nexus-sdk
2
-
3
- TypeScript SDK for creating plugins and modules compatible with [Nexus Backend](https://gitlab.gzl10.com/oss/nexus-backend).
4
-
5
- Define module and plugin manifests with full type safety without depending on the full `@gzl10/nexus-backend` package.
6
-
7
- ## Installation
8
-
9
- ```bash
10
- npm install @gzl10/nexus-sdk
11
- # or
12
- pnpm add @gzl10/nexus-sdk
13
- ```
14
-
15
- ## Usage
16
-
17
- ### Define a module
18
-
19
- ```typescript
20
- import type { ModuleManifest, PluginAuthRequest } from '@gzl10/nexus-sdk'
21
-
22
- export const tasksModule: ModuleManifest = {
23
- name: 'tasks',
24
- label: 'Tasks',
25
- icon: 'mdi:checkbox-marked-outline',
26
- description: 'Task management',
27
-
28
- dependencies: ['users'],
29
-
30
- migrate: async (ctx) => {
31
- const { db, helpers } = ctx
32
- const exists = await db.schema.hasTable('tasks')
33
- if (!exists) {
34
- await db.schema.createTable('tasks', (table) => {
35
- table.string('id').primary()
36
- table.string('title').notNullable()
37
- table.text('description')
38
- table.boolean('completed').defaultTo(false)
39
- table.string('created_by').references('id').inTable('users')
40
- helpers.addTimestamps(table, db)
41
- })
42
- }
43
- },
44
-
45
- routes: (ctx) => {
46
- const router = ctx.createRouter()
47
- const { abilities, services } = ctx
48
-
49
- router.get('/', async (req: PluginAuthRequest, res) => {
50
- // Check permissions with CASL
51
- abilities.ForbiddenError.from(req.ability).throwUnlessCan('read', 'tasks')
52
-
53
- const tasks = await ctx.db('tasks').select('*')
54
-
55
- // Resolve user relations
56
- const userIds = [...new Set(tasks.map(t => t.created_by).filter(Boolean))]
57
- const users = await services.users?.findByIds(userIds) ?? []
58
-
59
- res.json({ tasks, users })
60
- })
61
-
62
- return router
63
- },
64
-
65
- entities: [
66
- {
67
- name: 'tasks',
68
- label: 'Tasks',
69
- labelField: 'title',
70
- listFields: { title: 'Title', completed: 'Completed' },
71
- formFields: {
72
- title: { label: 'Title', type: 'text', required: true },
73
- description: { label: 'Description', type: 'textarea' },
74
- completed: { label: 'Completed', type: 'checkbox' }
75
- }
76
- }
77
- ]
78
- }
79
- ```
80
-
81
- ### Define a plugin (groups modules)
82
-
83
- ```typescript
84
- import type { PluginManifest } from '@gzl10/nexus-sdk'
85
- import { tasksModule } from './modules/tasks'
86
- import { projectsModule } from './modules/projects'
87
-
88
- export const projectPlugin: PluginManifest = {
89
- name: '@myorg/nexus-projects',
90
- label: 'Projects',
91
- icon: 'mdi:folder-outline',
92
- category: 'content',
93
- version: '1.0.0',
94
- description: 'Project management plugin',
95
- modules: [tasksModule, projectsModule]
96
- }
97
- ```
98
-
99
- ## EntityDefinition (v0.2.0+)
100
-
101
- Define entities as single source of truth for DB, validation, UI, and CASL:
102
-
103
- ```typescript
104
- import type { EntityDefinition } from '@gzl10/nexus-sdk'
105
-
106
- export const postEntity: EntityDefinition = {
107
- table: 'cms_posts',
108
- label: 'Posts',
109
- labelField: 'title',
110
- timestamps: true,
111
- audit: true,
112
-
113
- fields: {
114
- id: {
115
- name: 'id',
116
- label: 'ID',
117
- input: 'hidden',
118
- db: { type: 'uuid', nullable: false, defaultFn: 'uuid' }
119
- },
120
- title: {
121
- name: 'title',
122
- label: 'Title',
123
- input: 'text',
124
- db: { type: 'string', size: 200, nullable: false },
125
- validation: { required: true, min: 3, max: 200 },
126
- meta: { searchable: true, sortable: true }
127
- },
128
- content: {
129
- name: 'content',
130
- label: 'Content',
131
- input: 'markdown',
132
- db: { type: 'text', nullable: true }
133
- },
134
- status: {
135
- name: 'status',
136
- label: 'Status',
137
- input: 'select',
138
- db: { type: 'string', nullable: false, default: 'draft' },
139
- validation: { enum: ['draft', 'published', 'archived'] },
140
- options: {
141
- static: [
142
- { value: 'draft', label: 'Draft' },
143
- { value: 'published', label: 'Published' },
144
- { value: 'archived', label: 'Archived' }
145
- ]
146
- }
147
- }
148
- },
149
-
150
- casl: {
151
- ownership: { field: 'created_by' },
152
- permissions: {
153
- ADMIN: { actions: ['manage'] },
154
- EDITOR: { actions: ['create', 'read', 'update'] },
155
- VIEWER: { actions: ['read'] }
156
- }
157
- }
158
- }
159
- ```
160
-
161
- ### Code Generators
162
-
163
- Generate TypeScript interfaces from EntityDefinitions:
164
-
165
- ```typescript
166
- import { generateModel, generateModelsFile } from '@gzl10/nexus-sdk'
167
-
168
- // Generate TypeScript interface for a single entity
169
- const modelCode = generateModel(postEntity)
170
- // Creates: export interface Post { ... }
171
-
172
- // Generate models file for multiple entities
173
- const modelsFile = generateModelsFile([postEntity, userEntity])
174
- // Creates consolidated models file with all interfaces
175
- ```
176
-
177
- > **Note:** Zod schemas and migrations are now generated at runtime by `@gzl10/nexus-backend`.
178
-
179
- ## CLI (v0.5.0+)
180
-
181
- Generate TypeScript models from EntityDefinitions in your modules:
182
-
183
- ```bash
184
- # Install globally or use npx
185
- npx @gzl10/nexus-sdk generate
186
-
187
- # Generate for all modules in src/modules
188
- nexus-sdk generate --path src/modules
189
-
190
- # Generate for specific module
191
- nexus-sdk generate --module users
192
-
193
- # Watch mode for development
194
- nexus-sdk generate --watch
195
-
196
- # Dry run (preview without writing)
197
- nexus-sdk generate --dry-run --verbose
198
- ```
199
-
200
- ### CLI Options
201
-
202
- | Option | Default | Description |
203
- |--------|---------|-------------|
204
- | `-p, --path <dir>` | `src/modules` | Base modules directory |
205
- | `-m, --module <name>` | - | Generate only specific module |
206
- | `-o, --output <dir>` | `__generated__` | Output directory name |
207
- | `-w, --watch` | - | Watch for changes and regenerate |
208
- | `--dry-run` | - | Show what would be generated |
209
- | `--verbose` | - | Show detailed logs |
210
-
211
- ### Generated Files
212
-
213
- For each module with `definitions`, the CLI generates:
214
-
215
- ```text
216
- src/modules/posts/
217
- ├── index.ts # Module manifest with definitions
218
- └── __generated__/
219
- └── posts.models.ts # TypeScript interfaces
220
- ```
221
-
222
- ### Module Structure
223
-
224
- The CLI discovers modules by looking for `index.ts` files that export a `ModuleManifest` with `definitions`:
225
-
226
- ```typescript
227
- // src/modules/posts/index.ts
228
- import type { ModuleManifest, EntityDefinition } from '@gzl10/nexus-sdk'
229
-
230
- const postEntity: EntityDefinition = {
231
- type: 'collection',
232
- table: 'posts',
233
- // ...
234
- }
235
-
236
- export const postsModule: ModuleManifest = {
237
- name: 'posts',
238
- label: 'Posts',
239
- definitions: [postEntity]
240
- }
241
- ```
242
-
243
- ## Main Types
244
-
245
- ### Manifests & Context
246
-
247
- | Type | Description |
248
- |------|-------------|
249
- | `ModuleManifest` | Defines a module: routes, migrations, seeds, entities |
250
- | `PluginManifest` | Groups modules under a plugin with shared metadata |
251
- | `ModuleContext` | Context injected by Nexus: `db`, `logger`, `helpers`, `services`, `abilities` |
252
-
253
- ### Entity Definition System
254
-
255
- | Type | Description |
256
- |------|-------------|
257
- | `EntityDefinition` | Complete entity definition (DB, UI, validation, CASL) |
258
- | `FieldDefinition` | Field configuration with DB, UI, and validation settings |
259
- | `FieldDbConfig` | Database column configuration |
260
- | `FieldRelation` | Foreign key configuration |
261
- | `EntityCaslConfig` | CASL authorization configuration |
262
-
263
- ### Entity Types (Discriminated Union)
264
-
265
- | Type | Description |
266
- |------|-------------|
267
- | `CollectionEntity` | Full CRUD (users, posts) |
268
- | `ReferenceEntity` | Catalogs with admin CRUD (countries) |
269
- | `SingleEntity` | Singleton, update/read only (site_config) |
270
- | `ConfigEntity` | Per-module config, singleton |
271
- | `ExternalEntity` | Read-only from external systems |
272
- | `VirtualEntity` | Orchestration of multiple sources |
273
- | `ComputedEntity` | KPIs, calculated stats |
274
- | `ViewEntity` | Optimized read projections |
275
- | `EventEntity` | Audit logs, append-only |
276
- | `TempEntity` | TTL-based (cache, sessions) |
277
- | `ActionEntity` | Commands without persistence |
278
-
279
- ### Request & Auth
280
-
281
- | Type | Description |
282
- |------|-------------|
283
- | `AuthRequest<TUser, TAbility>` | Generic authenticated request |
284
- | `PluginAuthRequest` | Pre-typed `AuthRequest<BaseUser, AbilityLike>` for plugins |
285
- | `BaseUser` | User without password for relations |
286
- | `AbilityLike` | Generic CASL ability interface (`can`, `cannot`) |
287
-
288
- ### Services & Utilities
289
-
290
- | Type | Description |
291
- |------|-------------|
292
- | `EntityType` | Union of all entity type strings |
293
- | `PaginationParams` | Pagination input (`page`, `limit`) |
294
- | `PaginatedResult<T>` | Paginated response with metadata |
295
- | `ValidationSchema` | Generic Zod-compatible schema interface |
296
-
297
- ### ModuleContext
298
-
299
- The context provides access to:
300
-
301
- - `db` - Knex instance for queries
302
- - `logger` - Pino logger
303
- - `helpers` - Migration utilities (`addTimestamps`, `addColumnIfMissing`)
304
- - `createRouter()` - Express Router factory
305
- - `middleware.validate()` - Request validation with Zod schemas
306
- - `errors` - Error classes (`AppError`, `NotFoundError`, `UnauthorizedError`, etc.)
307
- - `abilities` - CASL helpers (`subject`, `ForbiddenError`)
308
- - `services` - Registered services (extensible)
309
- - `events` - EventEmitter for inter-module communication
310
- - `mail` - Email sending service
311
-
312
- ### Re-exported Types
313
-
314
- For convenience, the SDK re-exports commonly used types:
315
-
316
- - **Express:** `Request`, `Response`, `NextFunction`, `RequestHandler`, `Router`, `CookieOptions`
317
- - **Knex:** `Knex`, `KnexCreateTableBuilder`, `KnexAlterTableBuilder`, `KnexTransaction`
318
-
319
- ## License
320
-
321
- MIT © [Gonzalo Díez](https://www.gzl10.com)