@gzl10/nexus-sdk 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/dist/index.d.ts CHANGED
@@ -1,2257 +1,29 @@
1
1
  import { Knex } from 'knex';
2
2
  export { Knex } from 'knex';
3
- import { Request, Router, RequestHandler, Response } from 'express';
4
3
  export { CookieOptions, NextFunction, Request, RequestHandler, Response, Router } from 'express';
5
- import { Logger } from 'pino';
4
+ import { E as EntityType, D as DisplayMode, C as Category, a as EntityDefinition, b as CollectionEntityDefinition, R as ReferenceEntityDefinition, c as EventEntityDefinition, d as ConfigEntityDefinition, T as TempEntityDefinition, V as ViewEntityDefinition, e as TreeEntityDefinition, f as DagEntityDefinition, S as SingleEntityDefinition, A as ActionDefinition, g as ExternalEntityDefinition, h as VirtualEntityDefinition, i as ComputedEntityDefinition } from './entity-kPgsCMdR.js';
5
+ export { w as AbilityLike, $ as ActionEntityDefinition, ab as ActionEntityService, aN as ActionInjection, aG as AdaptersContext, aP as AppManifest, y as AuthRequest, o as BaseAuthRequest, a0 as BaseEntityService, ai as BaseMailService, ae as BaseRolesService, p as BaseUser, af as BaseUsersService, B as BatchConfig, n as BatchLogEntry, m as BatchProgressEvent, aW as CATEGORIES, aX as CATEGORY_ORDER, t as CaslAction, aM as CategoryMeta, a1 as CollectionEntityService, a8 as ComputedEntityService, aD as ConfigContext, a5 as ConfigEntityService, at as ContextCache, aq as ContextCrypto, ax as ContextHelpers, as as ContextLRUCache, ar as ContextLRUCacheOptions, aw as ContextSocket, aA as CoreContext, ak as CoreServices, ad as CreateEntityServiceOptions, aL as CustomRouteDefinition, I as DatabaseAdapter, aB as DbContext, aE as EngineContext, W as EntityAction, v as EntityCaslConfig, az as EntityController, ay as EntityHandler, H as EntityQuery, ac as EntityServiceHooks, ap as EventEmitterLike, a3 as EventEntityService, a9 as ExternalEntityService, z as FilterOperators, G as FilterValue, x as ForbiddenErrorConstructor, F as ForbiddenErrorInstance, aI as HttpMethod, aj as LoggerReporter, M as ModuleAbilities, aH as ModuleContext, aO as ModuleManifest, ao as ModuleMiddlewares, U as ModuleRequirements, r as PaginatedResult, P as PaginationParams, q as PaginationParamsWithOffset, aR as PluginConfigField, aS as PluginConfigSchema, aQ as PluginEnvVar, aU as PluginManifest, aT as PluginSetupInfo, an as RateLimitOptions, a6 as ReferenceEntityService, aV as RegisterPluginOptions, Z as RetentionPolicy, u as RolePermission, aJ as RouteParameterDefinition, aK as RouteResponseDefinition, aC as RuntimeContext, j as SSEHelper, l as SSEOptions, k as SSESender, Q as SchemaAdapter, O as SchemaAlterTableBuilder, K as SchemaColumnBuilder, J as SchemaColumnInfo, N as SchemaForeignKeyBuilder, L as SchemaTableBuilder, Y as SeedConfig, ag as SendMailOptions, ah as SendMailResult, aF as ServicesContext, a4 as SingleEntityService, av as SocketIOBroadcastOperator, au as SocketIOServer, X as StandaloneAction, a2 as TempEntityService, _ as TempRetentionPolicy, am as ValidateSchemas, s as ValidationDetail, al as ValidationSchema, a7 as ViewEntityService, aa as VirtualEntityService } from './entity-kPgsCMdR.js';
6
+ import { L as LocalizedString, I as InputType, F as FieldCondition, a as FieldMeta } from './field-CngHXora.js';
7
+ export { A as AuthProviderInfo, b as AuthProviderService, C as ConditionalBoolean, D as DbType, E as EntityIndex, c as FieldDbConfig, i as FieldDefinition, f as FieldOptions, d as FieldRelation, h as FieldStorageConfig, e as FieldValidationConfig, g as getCountLabel, k as refMaster, j as refOptions, r as resolveLocalized } from './field-CngHXora.js';
8
+ import 'pino';
6
9
 
7
10
  /**
8
- * Type guards and utilities for EntityDefinitions
9
- */
10
-
11
- /**
12
- * Entities that persist in the local DB with their own table.
13
- * Excludes: action, external, virtual, computed, single (uses shared table).
14
- */
15
- type PersistentEntityDefinition = CollectionEntityDefinition | ReferenceEntityDefinition | EventEntityDefinition | ConfigEntityDefinition | TempEntityDefinition | ViewEntityDefinition;
16
- /**
17
- * Entities without local persistence (read-only or without DB)
18
- */
19
- type NonPersistentEntityDefinition = ActionEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition | ComputedEntityDefinition;
20
- /**
21
- * Type guard to check whether an entity persists in the local DB with its own table
22
- */
23
- declare function isPersistentEntity(entity: EntityDefinition): entity is PersistentEntityDefinition;
24
- /**
25
- * Type guard to check whether it is a singleton (uses shared sys_settings table)
26
- */
27
- declare function isSingletonEntity(entity: EntityDefinition): entity is SingleEntityDefinition;
28
- /**
29
- * Type guard to check whether an entity has its own table (for migrations)
30
- */
31
- declare function hasTable(entity: EntityDefinition): entity is PersistentEntityDefinition;
32
- /**
33
- * Gets an entity name in singular PascalCase.
34
- * 'cms_posts' → 'Post', 'rol_role_permissions' → 'RolePermission'
35
- */
36
- declare function getEntityName(entity: EntityDefinition): string;
37
- /**
38
- * Gets the CASL subject for an entity
39
- */
40
- declare function getEntitySubject(entity: PersistentEntityDefinition): string;
41
-
42
- /**
43
- * @gzl10/nexus-sdk
44
- *
45
- * SDK types for creating Nexus plugins and modules.
46
- * Use this package to define plugin/module manifests without
47
- * depending on the full @gzl10/nexus-backend package.
48
- */
49
- /**
50
- * Discriminated union of all entity types
51
- *
52
- * | Type | Persistence | CRUD | Primary use |
53
- * |------------|-------------|-----------------|----------------------------------|
54
- * | collection | Yes (DB) | Full | Business data (users, posts) |
55
- * | single | Yes (DB) | Update/Read | Global config (site_config) |
56
- * | external | No | Read | External data (stripe_customers) |
57
- * | virtual | No | Read | Orchestration (unified_customers)|
58
- * | computed | No/optional | Read | KPIs, stats |
59
- * | view | Yes/virtual | Read | Optimized reads (projections) |
60
- * | reference | Yes | Read (admin) | Catalogs (countries, currencies) |
61
- * | config | Yes | Update/Read | Per-module config |
62
- * | event | Yes | Append | Audit logs (audit_logs) |
63
- * | temp | No (TTL) | Read/Write | Cache, sessions (otp_codes) |
64
- * | action | No | Execute | Operations, workflows |
65
- */
66
-
67
- type KnexCreateTableBuilder = Knex.CreateTableBuilder;
68
- type KnexAlterTableBuilder = Knex.AlterTableBuilder;
69
- type KnexTransaction = Knex.Transaction;
70
- /**
71
- * Base authenticated request with user and CASL abilities (generic).
72
- * Use AuthRequest for the pre-typed version.
73
- */
74
- interface BaseAuthRequest<TUser = unknown, TAbility = unknown> extends Request {
75
- user: TUser;
76
- ability: TAbility;
77
- }
78
- /**
79
- * Base user without a password for plugin use.
80
- * Used to type relations with users (e.g., author, created_by).
81
- * Note: Multi-role support - roles are loaded via user_roles pivot table.
82
- */
83
- interface BaseUser {
84
- id: string;
85
- name: string;
86
- email: string;
87
- created_at: Date;
88
- updated_at: Date;
89
- created_by: string | null;
90
- updated_by: string | null;
91
- /** Extensibility: allows custom user properties */
92
- [key: string]: unknown;
93
- }
94
- /**
95
- * Pagination parameters for queries
96
- */
97
- interface PaginationParams {
98
- page: number;
99
- limit: number;
100
- }
101
- /**
102
- * Generic paginated result
103
- */
104
- interface PaginatedResult<T> {
105
- items: T[];
106
- total: number;
107
- page: number;
108
- limit: number;
109
- totalPages: number;
110
- hasNext: boolean;
111
- }
112
- /**
113
- * Standard validation error detail for API responses
114
- */
115
- interface ValidationDetail {
116
- path: string;
117
- message: string;
118
- }
119
- /**
120
- * Localized string supporting multiple languages.
121
- * Can be a simple string (single language) or object with language codes.
122
- *
123
- * @example
124
- * // Simple string (backward compatible)
125
- * label: 'Users'
126
- *
127
- * // Multi-language object
128
- * label: { en: 'Users', es: 'Usuarios' }
129
- */
130
- type LocalizedString = string | Record<string, string>;
131
- /**
132
- * Resolves a LocalizedString to the appropriate language.
133
- * Falls back to 'en', then first available, then empty string.
134
- *
135
- * @param value - The localized string to resolve
136
- * @param locale - The target locale (e.g., 'en', 'es')
137
- * @returns The resolved string for the given locale
138
- */
139
- declare function resolveLocalized(value: LocalizedString | undefined, locale: string): string;
140
- /**
141
- * Gets the appropriate label based on count (pluralization).
142
- * Returns labelPlural for count !== 1, label otherwise.
143
- *
144
- * @param label - Singular form
145
- * @param labelPlural - Plural form (optional, defaults to label)
146
- * @param count - The count to determine singular/plural
147
- * @param locale - The target locale
148
- * @returns The resolved string for the given count and locale
149
- */
150
- declare function getCountLabel(label: LocalizedString | undefined, labelPlural: LocalizedString | undefined, count: number, locale: string): string;
151
- /**
152
- * Database column types for Knex migrations.
153
- *
154
- * Maps to SQL column types:
155
- * - `string` → VARCHAR(size) - Use with `db.size` for length
156
- * - `text` → TEXT - Unlimited length text
157
- * - `integer` → INTEGER - Whole numbers
158
- * - `decimal` → DECIMAL(p,s) - Use with `db.precision` for scale
159
- * - `boolean` → BOOLEAN - True/false values
160
- * - `date` → DATE - Date without time
161
- * - `datetime` → DATETIME/TIMESTAMP - Date with time
162
- * - `json` → JSON/JSONB - Structured data (use for LocalizedString)
163
- * - `uuid` → UUID - Universally unique identifier
164
- * - `array` → Virtual only - No database column generated
165
- *
166
- * @example
167
- * ```typescript
168
- * db: { type: 'string', size: 100 } // VARCHAR(100)
169
- * db: { type: 'decimal', precision: [10, 2] } // DECIMAL(10,2)
170
- * db: { type: 'json', nullable: false } // JSON NOT NULL
171
- * ```
172
- */
173
- type DbType = 'string' | 'text' | 'integer' | 'decimal' | 'boolean' | 'date' | 'datetime' | 'json' | 'uuid' | 'array';
174
- /**
175
- * UI input types that determine which form component renders.
176
- *
177
- * **Text inputs:**
178
- * - `text` → NInput - Single line text
179
- * - `email` → NInput with email validation
180
- * - `password` → NInput with password mask
181
- * - `url` → NInput with URL validation
182
- * - `tel` → NInput for phone numbers
183
- *
184
- * **Numeric inputs:**
185
- * - `number` → NInputNumber - Integer values
186
- * - `decimal` → NInputNumber with decimal precision
187
- *
188
- * **Rich text:**
189
- * - `textarea` → NInput multiline
190
- * - `markdown` → MarkdownInput with preview
191
- * - `json` → JsonInput with syntax highlighting
192
- *
193
- * **Selection:**
194
- * - `select` → NSelect - Single selection dropdown
195
- * - `multiselect` → NSelect with multiple - Multiple selection
196
- * - `transfer` → NTransfer - Dual list selection
197
- * - `tags` → NSelect with tags - Free-form tags input
198
- *
199
- * **Boolean:**
200
- * - `checkbox` → NCheckbox
201
- * - `switch` → NSwitch - Toggle switch
202
- *
203
- * **Date/Time:**
204
- * - `date` → NDatePicker - Date only
205
- * - `datetime` → NDatePicker with time
206
- *
207
- * **Files:**
208
- * - `file` → FileInput - Single file upload
209
- * - `fileArray` → FileInput multiple - Multiple files
210
- * - `image` → ImageInput - Single image with preview
211
- * - `imageArray` → ImageInput multiple - Image gallery
212
- *
213
- * **Other:**
214
- * - `icon` → IconPicker - Icon selection from library
215
- *
216
- * @see {@link FieldOptions} for select/multiselect configuration
217
- * @see {@link FieldStorageConfig} for file/image configuration
218
- */
219
- type InputType = 'text' | 'email' | 'password' | 'url' | 'tel' | 'number' | 'decimal' | 'textarea' | 'markdown' | 'json' | 'select' | 'multiselect' | 'transfer' | 'tags' | 'checkbox' | 'switch' | 'date' | 'datetime' | 'file' | 'fileArray' | 'image' | 'imageArray' | 'icon';
220
- /**
221
- * Database column configuration for Knex migrations.
222
- *
223
- * @example
224
- * ```typescript
225
- * // String with max length
226
- * db: { type: 'string', size: 100, nullable: false }
227
- *
228
- * // Decimal with precision
229
- * db: { type: 'decimal', precision: [10, 2] }
230
- *
231
- * // UUID with auto-generation
232
- * db: { type: 'uuid', defaultFn: 'uuid' }
233
- *
234
- * // Virtual field (no column in DB)
235
- * db: { type: 'array', virtual: true }
236
- * ```
237
- */
238
- interface FieldDbConfig {
239
- /** Column type for the database */
240
- type: DbType;
241
- /** If true, no database column is created (for computed/virtual fields like role_ids) */
242
- virtual?: boolean;
243
- /** String length for VARCHAR columns */
244
- size?: number;
245
- /** Precision and scale for DECIMAL: [precision, scale] e.g., [10, 2] for DECIMAL(10,2) */
246
- precision?: [number, number];
247
- /** Allow NULL values. Inferred from `required` if not specified */
248
- nullable?: boolean;
249
- /** Create unique constraint on this column */
250
- unique?: boolean;
251
- /** Static default value for the column */
252
- default?: unknown;
253
- /** Database function for default value: 'now' for timestamps, 'uuid' for auto-generated UUIDs */
254
- defaultFn?: 'now' | 'uuid';
255
- /** Create index on this column for faster queries */
256
- index?: boolean;
257
- }
258
- /**
259
- * Foreign key relationship configuration.
260
- *
261
- * Defines how this field references another table in the database.
262
- * Used for both migrations (FK constraint) and UI (dropdown options).
263
- *
264
- * @example
265
- * ```typescript
266
- * // Basic relation to users table
267
- * relation: { table: 'users' }
268
- *
269
- * // With cascade delete
270
- * relation: { table: 'categories', onDelete: 'CASCADE' }
271
- *
272
- * // Reference non-id column
273
- * relation: { table: 'countries', column: 'code' }
274
- * ```
275
- */
276
- interface FieldRelation {
277
- /** Target table name */
278
- table: string;
279
- /** Target column name (default: 'id') */
280
- column?: string;
281
- /** Action when referenced row is deleted */
282
- onDelete?: 'CASCADE' | 'RESTRICT' | 'SET NULL' | 'NO ACTION';
283
- /** Action when referenced row's key is updated */
284
- onUpdate?: 'CASCADE' | 'RESTRICT' | 'SET NULL' | 'NO ACTION';
285
- }
286
- /**
287
- * Validation rules for runtime and form validation.
288
- *
289
- * Used to generate Zod schemas for API validation and
290
- * form validation rules in the UI.
291
- *
292
- * @example
293
- * ```typescript
294
- * // String length constraints
295
- * validation: { min: 1, max: 200 }
296
- *
297
- * // Email format
298
- * validation: { format: 'email' }
299
- *
300
- * // Regex pattern
301
- * validation: { pattern: '^[a-z0-9-]+$' }
302
- *
303
- * // Enum values
304
- * validation: { enum: ['draft', 'published', 'archived'] }
305
- * ```
306
- */
307
- interface FieldValidationConfig {
308
- /** Minimum value (numbers) or minimum length (strings) */
309
- min?: number;
310
- /** Maximum value (numbers) or maximum length (strings) */
311
- max?: number;
312
- /** Regular expression pattern for validation */
313
- pattern?: string;
314
- /** Predefined format validation */
315
- format?: 'email' | 'url' | 'uuid' | 'slug';
316
- /** Allowed values (creates enum constraint) */
317
- enum?: string[];
318
- }
319
- /**
320
- * Configuration for select, multiselect, and dropdown fields.
321
- *
322
- * Options can be loaded from:
323
- * - Static array defined in the field
324
- * - Dynamic endpoint (API call)
325
- * - Related entity via `relation` config
326
- *
327
- * @example
328
- * ```typescript
329
- * // Static options
330
- * options: {
331
- * static: [
332
- * { value: 'draft', label: { en: 'Draft', es: 'Borrador' } },
333
- * { value: 'published', label: { en: 'Published', es: 'Publicado' } }
334
- * ]
335
- * }
336
- *
337
- * // Dynamic from API
338
- * options: {
339
- * endpoint: '/api/categories',
340
- * valueField: 'id',
341
- * labelField: 'name'
342
- * }
343
- *
344
- * // Allow creating new options (combobox)
345
- * options: { allowCreate: true, endpoint: '/api/tags' }
346
- * ```
347
- */
348
- interface FieldOptions {
349
- /** API endpoint for dynamic options (GET request) */
350
- endpoint?: string;
351
- /** Field to use as option value (default: 'id') */
352
- valueField?: string;
353
- /** Field to use as option label (default: 'name') */
354
- labelField?: string;
355
- /** Static list of options */
356
- static?: Array<{
357
- value: string;
358
- label: LocalizedString;
359
- }>;
360
- /** Allow creating new options on-the-fly (renders combobox) */
361
- allowCreate?: boolean;
362
- }
363
- /**
364
- * Storage configuration for file and image upload fields.
365
- *
366
- * Controls upload behavior, file validation, and image processing.
367
- *
368
- * @example
369
- * ```typescript
370
- * // Image with thumbnails
371
- * storage: {
372
- * accept: 'image/*',
373
- * maxSize: 5 * 1024 * 1024, // 5MB
374
- * folder: 'avatars',
375
- * thumbnails: [{ width: 100, height: 100 }, { width: 300, height: 300 }]
376
- * }
377
- *
378
- * // PDF documents with deduplication
379
- * storage: {
380
- * accept: 'application/pdf',
381
- * maxSize: 20 * 1024 * 1024, // 20MB
382
- * folder: 'documents',
383
- * dedupe: true
384
- * }
385
- *
386
- * // Multiple images
387
- * storage: {
388
- * accept: 'image/*',
389
- * maxFiles: 10,
390
- * folder: 'gallery'
391
- * }
392
- * ```
393
- */
394
- interface FieldStorageConfig {
395
- /** Accepted MIME types (e.g., 'image/*', 'application/pdf', 'image/png,image/jpeg') */
396
- accept?: string;
397
- /** Maximum file size in bytes (default: 10MB = 10485760) */
398
- maxSize?: number;
399
- /** Maximum number of files for array types (fileArray, imageArray) */
400
- maxFiles?: number;
401
- /** Folder/prefix in storage bucket (e.g., 'avatars', 'documents/contracts') */
402
- folder?: string;
403
- /** Thumbnail sizes to auto-generate for images. Creates resized versions on upload */
404
- thumbnails?: Array<{
405
- width: number;
406
- height: number;
407
- }>;
408
- /** Enable SHA256 hash deduplication - if identical file exists, reuse it instead of uploading again */
409
- dedupe?: boolean;
410
- }
411
- /**
412
- * Field metadata for UI behavior and data operations.
413
- *
414
- * Controls how the field behaves in tables, forms, exports, and searches.
415
- *
416
- * @example
417
- * ```typescript
418
- * // Searchable and sortable title field
419
- * meta: { searchable: true, sortable: true, exportable: true }
420
- *
421
- * // Audit field (auto-populated)
422
- * meta: { auditField: 'created_by', showInForm: false }
423
- *
424
- * // Show only in edit form, not create
425
- * meta: { showInForm: 'edit' }
426
- *
427
- * // Hidden from tables but visible in forms
428
- * meta: { showInDisplay: false }
429
- * ```
430
- */
431
- interface FieldMeta {
432
- /** Enable sorting by this field in table columns */
433
- sortable?: boolean;
434
- /** Include this field in search/filter operations */
435
- searchable?: boolean;
436
- /** Include this field in CSV/Excel exports */
437
- exportable?: boolean;
438
- /** Mark as audit field (auto-populated with user ID on create/update) */
439
- auditField?: 'created_by' | 'updated_by';
440
- /** Show in display components (Table, List, Masonry). Default: true */
441
- showInDisplay?: boolean;
442
- /** Show in forms. Use 'create'/'edit' for conditional display. Default: true */
443
- showInForm?: boolean | 'create' | 'edit';
444
- }
445
- /**
446
- * Conditional boolean - can be static or evaluated at runtime based on record
447
- * Functions are evaluated in backend before serializing to DTO
448
- */
449
- type ConditionalBoolean = boolean | ((record: Record<string, unknown>) => boolean);
450
- /**
451
- * Complete field definition for entity fields.
452
- *
453
- * This is the core building block for defining entity schemas in Nexus.
454
- * Contains all the information needed for:
455
- * - **UI**: Form inputs, table columns, list displays
456
- * - **Database**: Knex migrations, column types, constraints
457
- * - **Validation**: Zod schemas, runtime validation rules
458
- *
459
- * @example
460
- * ```typescript
461
- * const titleField: FieldDefinition = {
462
- * name: 'title',
463
- * label: { en: 'Title', es: 'Título' },
464
- * input: 'text',
465
- * required: true,
466
- * db: { type: 'string', nullable: false },
467
- * validation: { min: 1, max: 200 }
468
- * }
469
- * ```
470
- *
471
- * @see {@link FieldDefinitionDTO} for the serializable API response version
472
- * @see {@link EntityDefinition} for the parent entity structure
473
- */
474
- interface FieldDefinition {
475
- /**
476
- * Field identifier used as the database column name.
477
- * Should be snake_case for database compatibility.
478
- * @example 'title', 'created_at', 'author_id'
479
- */
480
- name: string;
481
- /**
482
- * Human-readable label displayed in forms, tables, and lists.
483
- * Supports localization with `{ en: '...', es: '...' }` format.
484
- */
485
- label: LocalizedString;
486
- /**
487
- * Input type that determines which UI component renders this field.
488
- * @see {@link InputType} for all available input types
489
- */
490
- input: InputType;
491
- /**
492
- * Placeholder text shown in empty input fields.
493
- * Supports localization.
494
- */
495
- placeholder?: LocalizedString;
496
- /**
497
- * Help text displayed below the input field.
498
- * Use for additional guidance or format hints.
499
- */
500
- hint?: LocalizedString;
501
- /**
502
- * Controls field visibility in the UI.
503
- * - `true`: Always hidden
504
- * - `false`: Always visible
505
- * - `(record) => boolean`: Dynamic based on record values
506
- * @example hidden: (record) => record.type === 'draft'
507
- */
508
- hidden?: ConditionalBoolean;
509
- /**
510
- * Controls whether the field is read-only in forms.
511
- * - `true`: Always disabled
512
- * - `false`: Always editable
513
- * - `(record) => boolean`: Dynamic based on record values
514
- * @example disabled: (record) => record.status === 'published'
515
- */
516
- disabled?: ConditionalBoolean;
517
- /**
518
- * Marks the field as required for form validation.
519
- * - `true`: Always required
520
- * - `false`: Always optional
521
- * - `(record) => boolean`: Dynamic based on record values
522
- * @example required: (record) => record.is_featured === true
523
- */
524
- required?: ConditionalBoolean;
525
- /**
526
- * Default value used when creating new records.
527
- * Type should match the field's expected data type.
528
- */
529
- defaultValue?: unknown;
530
- /**
531
- * Database column configuration for Knex migrations.
532
- * Optional for virtual fields, actions, or external data sources.
533
- * @see {@link FieldDbConfig}
534
- */
535
- db?: FieldDbConfig;
536
- /**
537
- * Foreign key relationship configuration.
538
- * Defines how this field relates to another entity's table.
539
- * @see {@link FieldRelation}
540
- */
541
- relation?: FieldRelation;
542
- /**
543
- * Validation rules applied during form submission and API requests.
544
- * Used to generate Zod schemas for runtime validation.
545
- * @see {@link FieldValidationConfig}
546
- */
547
- validation?: FieldValidationConfig;
548
- /**
549
- * Configuration for select inputs and relationship dropdowns.
550
- * Defines static options or dynamic data sources.
551
- * @see {@link FieldOptions}
552
- */
553
- options?: FieldOptions;
554
- /**
555
- * Storage configuration for file and image inputs.
556
- * Defines upload constraints, folders, and thumbnail generation.
557
- * @see {@link FieldStorageConfig}
558
- */
559
- storage?: FieldStorageConfig;
560
- /**
561
- * Additional metadata for categorization and UI organization.
562
- * @see {@link FieldMeta}
563
- */
564
- meta?: FieldMeta;
565
- /**
566
- * Additional props passed directly to the form input component.
567
- * Allows customization of Naive UI component behavior.
568
- * @example inputProps: { rows: 5, showCount: true }
569
- */
570
- inputProps?: Record<string, unknown>;
571
- /**
572
- * Additional props passed to display components (table columns, list items).
573
- * Use `order` prop to control field position in tables.
574
- * @example displayProps: { order: 1, width: 200 }
575
- */
576
- displayProps?: Record<string, unknown>;
577
- }
578
- /**
579
- * Composite table index definition for database migrations.
580
- *
581
- * Creates multi-column indexes for optimized queries.
582
- *
583
- * @example
584
- * ```typescript
585
- * // Composite index for frequent queries
586
- * indexes: [{ columns: ['tenant_id', 'created_at'] }]
587
- *
588
- * // Unique constraint on multiple columns
589
- * indexes: [{ columns: ['user_id', 'role_id'], unique: true }]
590
- * ```
591
- */
592
- interface EntityIndex {
593
- /** Column names to include in the composite index */
594
- columns: string[];
595
- /** Create unique constraint on the column combination */
596
- unique?: boolean;
597
- }
598
- /**
599
- * Standard CASL authorization actions.
600
- *
601
- * - `manage` - Full access (superuser wildcard, includes all actions)
602
- * - `create` - Create new records
603
- * - `read` - Read/view records
604
- * - `update` - Modify existing records
605
- * - `delete` - Remove records
606
- * - `execute` - Execute actions/operations on records
607
- *
608
- * @see {@link RolePermission} for role-based permission configuration
609
- * @see https://casl.js.org/v6/en/guide/intro
610
- */
611
- type CaslAction = 'manage' | 'create' | 'read' | 'update' | 'delete' | 'execute';
612
- /**
613
- * Ownership condition for CASL permissions.
614
- *
615
- * Automatically generates conditions that restrict access to records
616
- * owned by the current user.
617
- *
618
- * @example
619
- * ```typescript
620
- * // Users can only edit their own posts
621
- * ownership: { field: 'author_id' }
622
- * // Generates: { author_id: user.id }
623
- *
624
- * // Match against user's email instead of id
625
- * ownership: { field: 'owner_email', userField: 'email' }
626
- * // Generates: { owner_email: user.email }
627
- * ```
628
- */
629
- interface OwnershipCondition {
630
- /** Entity field that contains the owner reference (e.g., 'author_id', 'created_by') */
631
- field: string;
632
- /** User property to compare against (default: 'id'). Use 'email' for email-based ownership */
633
- userField?: string;
634
- }
635
- /**
636
- * CASL permission definition for a specific role.
637
- *
638
- * Defines what actions a role can perform on an entity,
639
- * optionally with conditions and field restrictions.
640
- *
641
- * @example
642
- * ```typescript
643
- * // Editor can read and update, but not delete
644
- * EDITOR: {
645
- * actions: ['read', 'update'],
646
- * fields: ['title', 'content', 'status']
647
- * }
648
- *
649
- * // Viewer can only read published items
650
- * VIEWER: {
651
- * actions: ['read'],
652
- * conditions: { status: 'published' }
653
- * }
654
- *
655
- * // Deny delete for everyone except admin
656
- * EDITOR: {
657
- * actions: ['delete'],
658
- * inverted: true
659
- * }
660
- * ```
661
- */
662
- interface RolePermission {
663
- /** Actions this role can perform on the entity */
664
- actions: CaslAction[];
665
- /** CASL conditions to filter which records the permission applies to */
666
- conditions?: Record<string, unknown>;
667
- /** Restrict permission to specific fields. null = all fields allowed */
668
- fields?: string[] | null;
669
- /** If true, this is a denial rule (cannot) instead of allow (can) */
670
- inverted?: boolean;
671
- }
672
- /**
673
- * CASL authorization configuration for an entity.
674
- *
675
- * Defines role-based access control (RBAC) using CASL.
676
- * Permissions are evaluated at runtime for each request.
677
- *
678
- * @example
679
- * ```typescript
680
- * casl: {
681
- * subject: 'BlogPost',
682
- * ownership: { field: 'author_id' },
683
- * permissions: {
684
- * ADMIN: { actions: ['manage'] },
685
- * EDITOR: { actions: ['read', 'create', 'update'] },
686
- * AUTHOR: { actions: ['read', 'update', 'delete'] }, // with ownership
687
- * VIEWER: { actions: ['read'], conditions: { status: 'published' } }
688
- * },
689
- * sensitiveFields: ['internal_notes', 'analytics']
690
- * }
691
- * ```
692
- *
693
- * @see https://casl.js.org/v6/en/guide/intro
694
- */
695
- interface EntityCaslConfig {
696
- /** CASL subject name. Default: inferred from table (e.g., 'cms_posts' → 'CmsPost') */
697
- subject?: string;
698
- /** Ownership configuration for automatic user-based filtering */
699
- ownership?: OwnershipCondition;
700
- /** Role-based permissions. Keys are role names (ADMIN, EDITOR, VIEWER, etc.) */
701
- permissions?: Record<string, RolePermission>;
702
- /** Fields excluded from 'read' permission unless explicitly granted. Use for PII or internal data */
703
- sensitiveFields?: string[];
704
- }
705
- /**
706
- * Action linked to an entity record.
707
- * Defines operations that act on existing records.
708
- *
709
- * Generated route: GET|POST /{entityRoutePrefix}/{key}/:id
710
- *
711
- * @example
712
- * ```typescript
713
- * actions: [{
714
- * key: 'download',
715
- * label: 'Download File',
716
- * icon: 'mdi:download',
717
- * method: 'GET',
718
- * select: ['id', 'filename', 'mimetype'],
719
- * handler: async (ctx, input, _req, res) => {
720
- * const { _record: file, _authUserId } = input
721
- * // file and _authUserId are injected automatically
722
- * }
723
- * }]
724
- * ```
725
- */
726
- interface EntityAction {
727
- /** Unique key for the route (e.g., 'download' → /download/:id) */
728
- key: string;
729
- /** Label for the UI */
730
- label: LocalizedString;
731
- /** Iconify icon (default: 'mdi:play') */
732
- icon?: string;
733
- /** HTTP method (default: 'POST') */
734
- method?: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
735
- /** Fields to select from the record (default: all) */
736
- select?: string[];
737
- /** Body validation schema (optional) */
738
- inputSchema?: ValidationSchema;
739
- /** Output schema (documentation) */
740
- outputSchema?: ValidationSchema;
741
- /** Custom middleware (e.g., multer) */
742
- middleware?: (ctx: ModuleContext) => RequestHandler | RequestHandler[];
743
- /**
744
- * Handler - receives _record and _authUserId automatically.
745
- * @param ctx - Module context
746
- * @param input - Validated body + _record + _authUserId
747
- * @param req - Express Request
748
- * @param res - Express Response (for streams, custom headers)
749
- */
750
- handler: (ctx: ModuleContext, input: unknown, req?: Request, res?: Response) => Promise<unknown>;
751
- /**
752
- * Skip authentication for this action.
753
- * Use for public endpoints like thumbnails, previews, etc.
754
- * @default false
755
- */
756
- skipAuth?: boolean;
757
- /**
758
- * CASL permissions specific to this action.
759
- * If not defined, it inherits permissions from the parent entity.
760
- * Ignored if skipAuth is true.
761
- */
762
- casl?: {
763
- /** CASL action (default: 'execute') */
764
- action?: string;
765
- /** Additional conditions */
766
- conditions?: Record<string, unknown>;
767
- };
768
- }
769
- /**
770
- * Base properties shared by all EntityDefinition types.
771
- *
772
- * @internal This interface is not exported directly. Use specific entity types instead.
773
- * @see {@link CollectionEntityDefinition}
774
- * @see {@link SingleEntityDefinition}
775
- * @see {@link ReferenceEntityDefinition}
776
- */
777
- interface BaseEntityDefinition {
778
- /** Database table name with module prefix (e.g., 'cms_posts', 'auth_users') */
779
- table: string;
780
- /** Display name for the UI (singular form) */
781
- label: LocalizedString;
782
- /** Display name for the UI (plural form, used in navigation menus and list headers) */
783
- labelPlural?: LocalizedString;
784
- /** Field definitions keyed by field name. See {@link FieldDefinition} */
785
- fields: Record<string, FieldDefinition>;
786
- /** CASL authorization configuration for role-based access control */
787
- casl?: EntityCaslConfig;
788
- /** API route prefix. Default: inferred from table name (e.g., 'cms_posts' → '/cms-posts') */
789
- routePrefix?: string;
790
- /** UI sidebar ordering. Lower values appear first (default: 999) */
791
- order?: number;
792
- /** UI display mode: 'table' for data grids, 'list' for cards, 'masonry' for image galleries */
793
- displayMode?: DisplayMode;
794
- /** If true, hides from UI sidebar but entity remains accessible via API */
795
- hidden?: boolean;
796
- }
797
- /**
798
- * Collection entity - the most common entity type with full CRUD operations.
799
- *
800
- * Use for business data like users, posts, orders, products, etc.
801
- * This is the default type when `type` is not specified.
802
- *
803
- * @example
804
- * ```typescript
805
- * const posts: CollectionEntityDefinition = {
806
- * type: 'collection', // optional, default
807
- * table: 'cms_posts',
808
- * label: { en: 'Post', es: 'Publicación' },
809
- * labelField: 'title',
810
- * timestamps: true,
811
- * audit: true,
812
- * softDelete: true,
813
- * fields: { ... }
814
- * }
815
- * ```
816
- */
817
- interface CollectionEntityDefinition extends BaseEntityDefinition {
818
- /** Entity type. Default: 'collection' */
819
- type?: 'collection';
820
- /** Field used as display label in dropdowns, references, and breadcrumbs (e.g., 'name', 'title') */
821
- labelField: string;
822
- /** Auto-add created_at and updated_at timestamp columns */
823
- timestamps?: boolean;
824
- /** Auto-add created_by and updated_by user reference columns */
825
- audit?: boolean;
826
- /** Composite database indexes for query optimization */
827
- indexes?: EntityIndex[];
828
- /** Use soft delete (deleted_at column) instead of physical DELETE */
829
- softDelete?: boolean;
830
- /** Custom actions that operate on entity records (e.g., download, publish, archive) */
831
- actions?: EntityAction[];
832
- /** Show "Create" button in UI. Set false for entities created only via API/actions */
833
- allowCreate?: boolean;
834
- /** Allow editing in UI. Set false for read-only display entities */
835
- allowEdit?: boolean;
836
- }
837
- /**
838
- * Singleton entity - stores a single record in the shared sys_settings table.
839
- *
840
- * Perfect for application-wide configuration like site settings, SMTP config,
841
- * feature flags, etc. Data is stored as JSON in a key-value format.
842
- *
843
- * @example
844
- * ```typescript
845
- * const siteConfig: SingleEntityDefinition = {
846
- * type: 'single',
847
- * key: 'site_config',
848
- * label: { en: 'Site Configuration', es: 'Configuración del Sitio' },
849
- * defaults: { siteName: 'My App', maintenanceMode: false },
850
- * fields: {
851
- * siteName: { name: 'siteName', label: 'Site Name', input: 'text', ... },
852
- * logo: { name: 'logo', label: 'Logo', input: 'image', ... }
853
- * }
854
- * }
855
- * // Stored in sys_settings: { key: 'site_config', value: '{"siteName":"My App","logo":"..."}' }
856
- * ```
857
- */
858
- interface SingleEntityDefinition {
859
- /** Entity type identifier */
860
- type: 'single';
861
- /** Unique key in sys_settings table (e.g., 'site_config', 'smtp_settings', 'feature_flags') */
862
- key: string;
863
- /** Display name for the UI (singular form) */
864
- label: LocalizedString;
865
- /** Display name for the UI (plural form) */
866
- labelPlural?: LocalizedString;
867
- /** Field definitions describing the JSON structure */
868
- fields: Record<string, FieldDefinition>;
869
- /** Default values applied when the setting is first accessed */
870
- defaults?: Record<string, unknown>;
871
- /** CASL authorization for read/update operations */
872
- casl?: EntityCaslConfig;
873
- /** API route prefix. Default: inferred from key */
874
- routePrefix?: string;
875
- /** Custom actions for this singleton (e.g., 'test-smtp', 'reset-defaults') */
876
- actions?: EntityAction[];
877
- }
878
- /**
879
- * Reference entity - read-only catalogs for lookups and dropdowns.
880
- *
881
- * Use for relatively static data like countries, currencies, categories, statuses.
882
- * Data can be seeded from code and optionally edited by admins.
883
- *
884
- * @example
885
- * ```typescript
886
- * const countries: ReferenceEntityDefinition = {
887
- * type: 'reference',
888
- * table: 'ref_countries',
889
- * label: { en: 'Country', es: 'País' },
890
- * labelField: 'name',
891
- * seed: [
892
- * { code: 'US', name: 'United States' },
893
- * { code: 'ES', name: 'Spain' }
894
- * ],
895
- * allowAdminEdit: true,
896
- * fields: { ... }
897
- * }
898
- * ```
899
- */
900
- interface ReferenceEntityDefinition extends BaseEntityDefinition {
901
- /** Entity type identifier */
902
- type: 'reference';
903
- /** Field used as display label in dropdowns (e.g., 'name', 'title') */
904
- labelField: string;
905
- /** Auto-add created_at and updated_at timestamp columns */
906
- timestamps?: boolean;
907
- /** Composite database indexes */
908
- indexes?: EntityIndex[];
909
- /** Initial seed data inserted on first migration/startup */
910
- seed?: Array<Record<string, unknown>>;
911
- /** Allow admin users to edit reference data. Default: false (read-only) */
912
- allowAdminEdit?: boolean;
913
- }
914
- /**
915
- * Event entity - append-only logs for audit trails and analytics.
916
- *
917
- * Records can only be created (appended), never updated or deleted.
918
- * Supports automatic retention policies to prevent unbounded growth.
919
- *
920
- * @example
921
- * ```typescript
922
- * const auditLogs: EventEntityDefinition = {
923
- * type: 'event',
924
- * table: 'sys_audit_logs',
925
- * label: { en: 'Audit Log', es: 'Log de Auditoría' },
926
- * labelField: 'action',
927
- * timestamps: true,
928
- * retention: { days: 90, maxRows: 100000 },
929
- * fields: {
930
- * action: { name: 'action', label: 'Action', input: 'text', ... },
931
- * user_id: { name: 'user_id', label: 'User', input: 'select', ... },
932
- * payload: { name: 'payload', label: 'Data', input: 'json', ... }
933
- * }
934
- * }
935
- * ```
936
- */
937
- interface EventEntityDefinition extends BaseEntityDefinition {
938
- /** Entity type identifier */
939
- type: 'event';
940
- /** Field used as display label in event lists (e.g., 'action', 'event_type') */
941
- labelField: string;
942
- /** Auto-add created_at column. Set false if you define timestamp fields explicitly */
943
- timestamps?: boolean;
944
- /** Automatic data retention to limit table growth */
945
- retention?: {
946
- /** Delete records older than N days */
947
- days?: number;
948
- /** Keep only the most recent N records (FIFO) */
949
- maxRows?: number;
950
- };
951
- }
952
- /**
953
- * Action entity - standalone commands/operations without data persistence.
954
- *
955
- * Use for operations like sending emails, generating reports, triggering workflows.
956
- * For actions that operate on existing records, use {@link EntityAction} instead.
957
- *
958
- * @example
959
- * ```typescript
960
- * const sendEmail: ActionEntityDefinition = {
961
- * type: 'action',
962
- * label: { en: 'Send Email', es: 'Enviar Email' },
963
- * icon: 'mdi:email-send',
964
- * fields: {
965
- * to: { name: 'to', label: 'To', input: 'email', required: true },
966
- * subject: { name: 'subject', label: 'Subject', input: 'text', required: true },
967
- * body: { name: 'body', label: 'Body', input: 'markdown' }
968
- * },
969
- * handler: async (ctx, input) => {
970
- * await ctx.services.mail.send(input)
971
- * return { success: true }
972
- * }
973
- * }
974
- * ```
975
- */
976
- interface ActionEntityDefinition {
977
- /** Entity type identifier */
978
- type: 'action';
979
- /** Display name for the action button/form */
980
- label: LocalizedString;
981
- /** Iconify icon identifier (default: 'mdi:play') */
982
- icon?: string;
983
- /** Form fields for action input. These don't create database columns */
984
- fields: Record<string, FieldDefinition>;
985
- /** Zod schema for validating input before execution */
986
- inputSchema?: ValidationSchema;
987
- /** Zod schema documenting the response structure */
988
- outputSchema?: ValidationSchema;
989
- /** HTTP method for the action endpoint (default: 'POST') */
990
- method?: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
991
- /** Custom middleware (e.g., multer for file uploads). Returns single or array of handlers */
992
- middleware?: (ctx: ModuleContext) => RequestHandler | RequestHandler[];
993
- /** Handler function that executes the action logic. Input includes _authUserId if authenticated */
994
- handler?: (ctx: ModuleContext, input: unknown, req?: Request, res?: Response) => Promise<unknown>;
995
- /** HTTP status code for success response. Default: 200, use 201 for resource creation */
996
- successStatus?: number;
997
- /** CASL authorization configuration */
998
- casl?: EntityCaslConfig;
999
- /** API route prefix for the action endpoint */
1000
- routePrefix?: string;
1001
- /** UI sidebar ordering. Lower values appear first (default: 999) */
1002
- order?: number;
1003
- }
1004
- /**
1005
- * External entity - read-only data from external APIs.
1006
- *
1007
- * Use for integrating third-party services like Stripe customers, GitHub repos,
1008
- * or any external REST API. Data is fetched on-demand through adapters.
1009
- *
1010
- * @example
1011
- * ```typescript
1012
- * const stripeCustomers: ExternalEntityDefinition = {
1013
- * type: 'external',
1014
- * label: { en: 'Stripe Customer', es: 'Cliente Stripe' },
1015
- * labelField: 'email',
1016
- * adapter: 'stripe',
1017
- * source: { endpoint: 'customers' },
1018
- * cache: { ttl: 300, invalidateOn: ['stripe.customer.*'] },
1019
- * fields: {
1020
- * id: { name: 'id', label: 'ID', input: 'text' },
1021
- * email: { name: 'email', label: 'Email', input: 'email' },
1022
- * name: { name: 'name', label: 'Name', input: 'text' }
1023
- * }
1024
- * }
1025
- * ```
1026
- */
1027
- interface ExternalEntityDefinition {
1028
- /** Entity type identifier */
1029
- type: 'external';
1030
- /** Display name for the UI (singular form) */
1031
- label: LocalizedString;
1032
- /** Display name for the UI (plural form) */
1033
- labelPlural?: LocalizedString;
1034
- /** Field used as display label in lists and references */
1035
- labelField: string;
1036
- /** Field definitions mapping to the external API response structure */
1037
- fields: Record<string, FieldDefinition>;
1038
- /** Registered adapter name that handles API communication (e.g., 'stripe', 'github', 'salesforce') */
1039
- adapter: string;
1040
- /** Configuration passed to the adapter */
1041
- source?: {
1042
- /** API endpoint or resource path */
1043
- endpoint?: string;
1044
- /** Additional adapter-specific configuration */
1045
- [key: string]: unknown;
1046
- };
1047
- /** Response caching to reduce API calls */
1048
- cache?: {
1049
- /** Cache time-to-live in seconds */
1050
- ttl: number;
1051
- /** Field used to generate unique cache keys */
1052
- key?: string;
1053
- /** Event patterns that invalidate cached data (e.g., ['stripe.customer.*']) */
1054
- invalidateOn?: string[];
1055
- };
1056
- /** CASL authorization for read operations */
1057
- casl?: EntityCaslConfig;
1058
- /** API route prefix */
1059
- routePrefix?: string;
1060
- /** UI sidebar ordering (default: 999) */
1061
- order?: number;
1062
- /** UI display mode: 'table', 'list', or 'masonry' */
1063
- displayMode?: DisplayMode;
1064
- }
1065
- /**
1066
- * Virtual entity - data aggregation across multiple sources.
1067
- *
1068
- * Use to create unified views combining local entities, external APIs, or computed data.
1069
- * Read-only, no persistence - data is resolved on each request.
1070
- *
1071
- * @example
1072
- * ```typescript
1073
- * const unifiedContacts: VirtualEntityDefinition = {
1074
- * type: 'virtual',
1075
- * label: { en: 'Contact', es: 'Contacto' },
1076
- * labelField: 'name',
1077
- * sources: ['crm_contacts', 'stripe_customers'],
1078
- * resolver: (sources, ctx) => {
1079
- * const contacts = sources.crm_contacts.map(c => ({ ...c, source: 'crm' }))
1080
- * const customers = sources.stripe_customers.map(c => ({ ...c, source: 'stripe' }))
1081
- * return [...contacts, ...customers]
1082
- * },
1083
- * fields: { ... }
1084
- * }
1085
- * ```
1086
- */
1087
- interface VirtualEntityDefinition {
1088
- /** Entity type identifier */
1089
- type: 'virtual';
1090
- /** Display name for the UI (singular form) */
1091
- label: LocalizedString;
1092
- /** Display name for the UI (plural form) */
1093
- labelPlural?: LocalizedString;
1094
- /** Field used as display label in lists and references */
1095
- labelField: string;
1096
- /** Unified field definitions for the combined schema */
1097
- fields: Record<string, FieldDefinition>;
1098
- /** Entity or table names to fetch data from */
1099
- sources: string[];
1100
- /** Custom function to combine and transform data from all sources */
1101
- resolver?: (sources: Record<string, unknown[]>, ctx: ModuleContext) => unknown[] | Promise<unknown[]>;
1102
- /** CASL authorization for read operations */
1103
- casl?: EntityCaslConfig;
1104
- /** API route prefix */
1105
- routePrefix?: string;
1106
- /** UI sidebar ordering (default: 999) */
1107
- order?: number;
1108
- /** UI display mode: 'table', 'list', or 'masonry' */
1109
- displayMode?: DisplayMode;
1110
- }
1111
- /**
1112
- * Computed entity - dynamically calculated data like KPIs, statistics, and metrics.
1113
- *
1114
- * Use for dashboards, reports, and analytics that derive from other data sources.
1115
- * Read-only, computed on-demand with optional caching.
1116
- *
1117
- * @example
1118
- * ```typescript
1119
- * const salesMetrics: ComputedEntityDefinition = {
1120
- * type: 'computed',
1121
- * label: { en: 'Sales Metrics', es: 'Métricas de Ventas' },
1122
- * isSingle: true, // returns single record with all metrics
1123
- * cache: { ttl: 60, invalidateOn: ['db.orders.*'] },
1124
- * compute: async (ctx) => [{
1125
- * totalSales: await ctx.db('orders').sum('total'),
1126
- * orderCount: await ctx.db('orders').count(),
1127
- * avgOrderValue: await ctx.db('orders').avg('total')
1128
- * }],
1129
- * fields: {
1130
- * totalSales: { name: 'totalSales', label: 'Total Sales', input: 'number' },
1131
- * orderCount: { name: 'orderCount', label: 'Orders', input: 'number' },
1132
- * avgOrderValue: { name: 'avgOrderValue', label: 'Avg Order', input: 'decimal' }
1133
- * }
1134
- * }
1135
- * ```
1136
- */
1137
- interface ComputedEntityDefinition {
1138
- /** Entity type identifier */
1139
- type: 'computed';
1140
- /** Display name for the UI (singular form) */
1141
- label: LocalizedString;
1142
- /** Display name for the UI (plural form) */
1143
- labelPlural?: LocalizedString;
1144
- /** Field used as display label (optional for computed entities) */
1145
- labelField?: string;
1146
- /** Field definitions for the computed result structure */
1147
- fields: Record<string, FieldDefinition>;
1148
- /** Function that computes and returns the data. Receives query params from URL */
1149
- compute?: (ctx: ModuleContext, params?: Record<string, unknown>) => Promise<unknown[]>;
1150
- /** Caching to avoid recomputation on every request */
1151
- cache?: {
1152
- /** Cache time-to-live in seconds. 0 = no caching */
1153
- ttl: number;
1154
- /** Event patterns that trigger cache invalidation */
1155
- invalidateOn?: string[];
1156
- };
1157
- /** If true, returns single object instead of array (for dashboards, system info) */
1158
- isSingle?: boolean;
1159
- /** CASL authorization for read operations */
1160
- casl?: EntityCaslConfig;
1161
- /** API route prefix */
1162
- routePrefix?: string;
1163
- /** UI sidebar ordering (default: 999) */
1164
- order?: number;
1165
- /** UI display mode: 'table', 'list', or 'masonry' */
1166
- displayMode?: DisplayMode;
1167
- }
1168
- /**
1169
- * View entity - read-only database view or projection.
1170
- *
1171
- * Use for optimized read patterns, denormalized data, or SQL VIEWs.
1172
- * Can reference actual database views or define virtual queries.
1173
- *
1174
- * @example
1175
- * ```typescript
1176
- * const orderSummary: ViewEntityDefinition = {
1177
- * type: 'view',
1178
- * table: 'v_order_summary', // actual SQL VIEW
1179
- * label: { en: 'Order Summary', es: 'Resumen de Pedidos' },
1180
- * labelField: 'order_number',
1181
- * sourceEntity: 'orders',
1182
- * query: (db) => db('orders')
1183
- * .select('orders.*', 'customers.name as customer_name')
1184
- * .join('customers', 'orders.customer_id', 'customers.id'),
1185
- * fields: { ... }
1186
- * }
1187
- * ```
1188
- */
1189
- interface ViewEntityDefinition {
1190
- /** Entity type identifier */
1191
- type: 'view';
1192
- /** Database table or SQL VIEW name */
1193
- table: string;
1194
- /** Display name for the UI (singular form) */
1195
- label: LocalizedString;
1196
- /** Display name for the UI (plural form) */
1197
- labelPlural?: LocalizedString;
1198
- /** Field used as display label in lists and references */
1199
- labelField: string;
1200
- /** Field definitions for the view columns */
1201
- fields: Record<string, FieldDefinition>;
1202
- /** Source entity this view derives from (for documentation) */
1203
- sourceEntity?: string;
1204
- /** SQL query string or Knex query builder function to define the view */
1205
- query?: string | ((db: Knex) => Knex.QueryBuilder);
1206
- /** CASL authorization for read operations */
1207
- casl?: EntityCaslConfig;
1208
- /** API route prefix */
1209
- routePrefix?: string;
1210
- /** UI sidebar ordering (default: 999) */
1211
- order?: number;
1212
- }
1213
- /**
1214
- * Config entity - scoped configuration settings.
1215
- *
1216
- * Similar to single entity but with scoping support for multi-tenant,
1217
- * per-module, or per-user configurations.
1218
- *
1219
- * @example
1220
- * ```typescript
1221
- * const moduleConfig: ConfigEntityDefinition = {
1222
- * type: 'config',
1223
- * table: 'module_configs',
1224
- * label: { en: 'Module Config', es: 'Config de Módulo' },
1225
- * scope: 'module',
1226
- * scopeField: 'module_name',
1227
- * defaults: { enableFeatureX: false, maxItems: 100 },
1228
- * timestamps: true,
1229
- * fields: {
1230
- * module_name: { name: 'module_name', label: 'Module', input: 'text' },
1231
- * enableFeatureX: { name: 'enableFeatureX', label: 'Feature X', input: 'switch' },
1232
- * maxItems: { name: 'maxItems', label: 'Max Items', input: 'number' }
1233
- * }
1234
- * }
1235
- * ```
1236
- */
1237
- interface ConfigEntityDefinition extends BaseEntityDefinition {
1238
- /** Entity type identifier */
1239
- type: 'config';
1240
- /** Field that identifies the configuration scope (e.g., 'module_name', 'tenant_id', 'user_id') */
1241
- scopeField?: string;
1242
- /** Configuration scope level: 'global', 'module', 'tenant', or 'user' */
1243
- scope?: 'global' | 'module' | 'tenant' | 'user';
1244
- /** Default values applied when config is first accessed */
1245
- defaults?: Record<string, unknown>;
1246
- /** Auto-add updated_at timestamp column */
1247
- timestamps?: boolean;
1248
- /** Auto-add updated_by user reference column */
1249
- audit?: boolean;
1250
- }
1251
- /**
1252
- * Temporary entity - short-lived data with automatic expiration.
1253
- *
1254
- * Use for OTP codes, session tokens, password reset links, cache entries.
1255
- * Records are automatically deleted after TTL expires.
1256
- *
1257
- * @example
1258
- * ```typescript
1259
- * const otpCodes: TempEntityDefinition = {
1260
- * type: 'temp',
1261
- * table: 'auth_otp_codes',
1262
- * label: { en: 'OTP Code', es: 'Código OTP' },
1263
- * ttl: 300, // 5 minutes
1264
- * indexes: [{ columns: ['user_id', 'code'] }],
1265
- * fields: {
1266
- * user_id: { name: 'user_id', label: 'User', input: 'select', ... },
1267
- * code: { name: 'code', label: 'Code', input: 'text', ... },
1268
- * expires_at: { name: 'expires_at', label: 'Expires', input: 'datetime', ... }
1269
- * }
1270
- * }
1271
- * ```
1272
- */
1273
- interface TempEntityDefinition extends BaseEntityDefinition {
1274
- /** Entity type identifier */
1275
- type: 'temp';
1276
- /** Time-to-live in seconds. Records are deleted after this duration */
1277
- ttl: number;
1278
- /** Field that stores the expiration timestamp (default: 'expires_at') */
1279
- ttlField?: string;
1280
- /** Field used as display label in lists (optional for temp entities) */
1281
- labelField?: string;
1282
- /** Database indexes for efficient lookups */
1283
- indexes?: EntityIndex[];
1284
- }
1285
- /**
1286
- * All available entity type identifiers.
1287
- *
1288
- * @see {@link EntityDefinition} for the full discriminated union with documentation
1289
- */
1290
- type EntityType = 'collection' | 'single' | 'external' | 'virtual' | 'computed' | 'view' | 'reference' | 'config' | 'event' | 'temp' | 'action';
1291
- /**
1292
- * Display mode for entity list views in the UI.
1293
- *
1294
- * - `table` - Data grid with columns, sorting, filtering (default)
1295
- * - `list` - Card-based vertical list
1296
- * - `masonry` - Pinterest-style image gallery grid
1297
- */
1298
- type DisplayMode = 'table' | 'list' | 'masonry';
1299
- /**
1300
- * Union of all entity definitions
1301
- *
1302
- * | Type | Persistence | CRUD | Primary use |
1303
- * |------------|-------------|-----------------|----------------------------------|
1304
- * | collection | Yes (DB) | Full | Business data (users, posts) |
1305
- * | single | Yes (DB) | Update/Read | Global config (site_config) |
1306
- * | external | No | Read | External data (stripe_customers) |
1307
- * | virtual | No | Read | Orchestration (unified_customers)|
1308
- * | computed | No/optional | Read | KPIs, stats |
1309
- * | view | Yes/virtual | Read | Optimized reads (projections) |
1310
- * | reference | Yes | Read (admin) | Catalogs (countries, currencies) |
1311
- * | config | Yes | Update/Read | Per-module config |
1312
- * | event | Yes | Append | Audit logs (audit_logs) |
1313
- * | temp | No (TTL) | Read/Write | Cache, sessions (otp_codes) |
1314
- * | action | No | Execute | Operations, workflows |
1315
- */
1316
- type EntityDefinition = CollectionEntityDefinition | SingleEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition | ComputedEntityDefinition | ViewEntityDefinition | ReferenceEntityDefinition | ConfigEntityDefinition | EventEntityDefinition | TempEntityDefinition | ActionEntityDefinition;
1317
- /**
1318
- * Requirements that must be met to activate a module.
1319
- *
1320
- * Modules with unmet requirements are skipped during initialization.
1321
- *
1322
- * @example
1323
- * ```typescript
1324
- * requirements: {
1325
- * env: ['STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET'],
1326
- * modules: ['auth', 'storage']
1327
- * }
1328
- * ```
1329
- */
1330
- interface ModuleRequirements {
1331
- /** Environment variables that must be defined */
1332
- env?: string[];
1333
- /** Other modules that must be loaded first */
1334
- modules?: string[];
1335
- }
1336
- /**
1337
- * Generic interface for CASL ability.
1338
- *
1339
- * Provides permission checking without directly depending on @casl/ability package.
1340
- * Used in request objects for authorization checks.
1341
- *
1342
- * @example
1343
- * ```typescript
1344
- * if (req.ability.can('update', post)) {
1345
- * // User can update this post
1346
- * }
1347
- * if (req.ability.cannot('delete', post)) {
1348
- * throw new ForbiddenError('Cannot delete post')
1349
- * }
1350
- * ```
1351
- */
1352
- interface AbilityLike {
1353
- /** Check if action is allowed on subject */
1354
- can: (action: string, subject: unknown) => boolean;
1355
- /** Check if action is denied on subject */
1356
- cannot: (action: string, subject: unknown) => boolean;
1357
- }
1358
- /**
1359
- * CASL ForbiddenError instance for throwing permission errors.
1360
- */
1361
- interface ForbiddenErrorInstance {
1362
- /** Throws ForbiddenError if action is not allowed on subject */
1363
- throwUnlessCan: (action: string, subject: unknown) => void;
1364
- }
1365
- /**
1366
- * CASL ForbiddenError constructor for creating error instances.
1367
- */
1368
- interface ForbiddenErrorConstructor {
1369
- /** Creates a ForbiddenErrorInstance bound to an ability */
1370
- from: (ability: any) => ForbiddenErrorInstance;
1371
- }
1372
- /**
1373
- * CASL abilities API available in module context.
1374
- *
1375
- * Allows modules to use CASL for authorization without importing @casl/ability directly.
1376
- *
1377
- * @example
1378
- * ```typescript
1379
- * // In module handler
1380
- * const ability = ctx.abilities.defineAbilityFor(user)
1381
- * ctx.abilities.ForbiddenError.from(ability).throwUnlessCan('update', post)
1382
- * ```
1383
- */
1384
- interface ModuleAbilities {
1385
- /** Creates CASL ability instance for a user based on their roles */
1386
- defineAbilityFor: (user: {
1387
- id: string;
1388
- } | null, ...args: unknown[]) => unknown;
1389
- /** Serializes CASL rules for client-side authorization (frontend) */
1390
- packRules: (ability: unknown) => unknown[];
1391
- /** Wraps an object as CASL subject for instance-level permission checks */
1392
- subject: (type: string, object: Record<string, unknown>) => unknown;
1393
- /** ForbiddenError constructor for throwUnlessCan pattern */
1394
- ForbiddenError: ForbiddenErrorConstructor;
1395
- }
1396
- /**
1397
- * Pre-typed authenticated request with BaseUser and AbilityLike.
1398
- *
1399
- * Use this type for route handlers that require authentication.
1400
- *
1401
- * @example
1402
- * ```typescript
1403
- * const handler = async (req: AuthRequest, res: Response) => {
1404
- * const userId = req.user.id
1405
- * if (req.ability.can('read', 'Post')) { ... }
1406
- * }
1407
- * ```
1408
- */
1409
- type AuthRequest = BaseAuthRequest<BaseUser, AbilityLike>;
1410
- /**
1411
- * Helper utilities available in module context.
1412
- *
1413
- * Provides common operations for migrations, ID generation, and path resolution.
1414
- */
1415
- interface ContextHelpers {
1416
- /** Generates a unique UUID v4 identifier */
1417
- generateId: () => string;
1418
- /** Adds created_at and updated_at columns to a table during migration */
1419
- addTimestamps: (table: Knex.CreateTableBuilder, db: Knex) => void;
1420
- /** Adds created_by and updated_by columns if they don't exist */
1421
- addAuditFieldsIfMissing: (db: Knex, tableName: string) => Promise<void>;
1422
- /** Adds is_default boolean field for config tables */
1423
- addConfigDefaultField: (db: Knex, tableName: string) => Promise<void>;
1424
- /** Adds a column only if it doesn't exist. Returns true if column was added */
1425
- addColumnIfMissing: (db: Knex, tableName: string, columnName: string, columnBuilder: (table: Knex.AlterTableBuilder) => void) => Promise<boolean>;
1426
- /** Gets the path to the nexus-backend library directory */
1427
- getLibPath: () => string;
1428
- /** Gets the path to the user's project root directory */
1429
- getProjectPath: () => string;
1430
- /** Returns current timestamp formatted for the DB driver (MySQL: 'YYYY-MM-DD HH:MM:SS', others: ISO 8601) */
1431
- nowTimestamp: (db: Knex) => string;
1432
- /** Formats a Date object for the current DB driver */
1433
- formatTimestamp: (db: Knex, date?: Date) => string;
1434
- /**
1435
- * Safely parses JSON with automatic error logging.
1436
- * Returns fallback value if parsing fails instead of throwing.
1437
- * @param jsonString - JSON string to parse
1438
- * @param fallback - Value to return if parsing fails
1439
- * @param context - Optional context for logging (e.g. { notificationId: 'x', context: 'markAsRead' })
1440
- */
1441
- safeJsonParse: <T>(jsonString: string, fallback: T, context?: Record<string, unknown>) => T;
1442
- }
1443
- /**
1444
- * Cryptographic utilities for password handling.
1445
- *
1446
- * Uses bcrypt with cost factor 12 for secure password hashing.
1447
- */
1448
- interface ContextCrypto {
1449
- /** Hashes a password using bcrypt (cost factor 12) */
1450
- hashPassword: (password: string) => Promise<string>;
1451
- /** Verifies password against bcrypt hash (timing-safe comparison) */
1452
- verifyPassword: (password: string, hash: string) => Promise<boolean>;
1453
- /** Pre-computed dummy hash for timing-safe comparison when user doesn't exist */
1454
- DUMMY_HASH: string;
1455
- }
1456
- /**
1457
- * Socket.IO real-time communication API.
1458
- *
1459
- * Provides access to WebSocket connections for real-time features.
1460
- * Only available if Socket.IO is initialized in the backend configuration.
1461
- */
1462
- /**
1463
- * Socket.IO Server interface (minimal type-safe subset)
1464
- * Avoids importing socket.io package as dependency in SDK
1465
- */
1466
- interface SocketIOServer {
1467
- /** Emits event to a specific room */
1468
- to(room: string): SocketIOBroadcastOperator;
1469
- /** Emits event to a specific room (alias for to) */
1470
- in(room: string): SocketIOBroadcastOperator;
1471
- /** Emits event to all connected clients */
1472
- emit(event: string, ...args: unknown[]): boolean;
1473
- /** Access to all connected sockets */
1474
- sockets: {
1475
- sockets: Map<string, unknown>;
1476
- };
1477
- }
1478
- /**
1479
- * Socket.IO Broadcast Operator interface (minimal type-safe subset)
1480
- */
1481
- interface SocketIOBroadcastOperator {
1482
- /** Emits event to all sockets in the room */
1483
- emit(event: string, ...args: unknown[]): void;
1484
- /** Fetches all sockets in the room */
1485
- fetchSockets(): Promise<unknown[]>;
1486
- }
1487
- interface ContextSocket {
1488
- /** Gets the Socket.IO server instance. Throws if not initialized */
1489
- getIO: () => SocketIOServer;
1490
- /** Checks if Socket.IO server is running */
1491
- isInitialized: () => boolean;
1492
- /** Checks if a specific user has active WebSocket connections */
1493
- isUserConnected: (userId: string) => boolean;
1494
- /** Gets the number of active sockets for a user (multiple tabs/devices) */
1495
- getUserSocketCount: (userId: string) => number;
1496
- /** Gets array of all connected user IDs */
1497
- getConnectedUsers: () => string[];
1498
- /** Joins a user to a custom room (all their sockets) */
1499
- joinUserToRoom: (userId: string, room: string) => boolean;
1500
- /** Removes a user from a custom room (all their sockets) */
1501
- removeUserFromRoom: (userId: string, room: string) => boolean;
1502
- /** Joins a specific socket to a room */
1503
- joinSocketToRoom: (socketId: string, room: string) => boolean;
1504
- /** Removes a specific socket from a room */
1505
- removeSocketFromRoom: (socketId: string, room: string) => boolean;
1506
- /** Gets all user IDs in a room */
1507
- getRoomMembers: (room: string) => Promise<string[]>;
1508
- /** Gets the number of sockets in a room */
1509
- getRoomSize: (room: string) => Promise<number>;
1510
- /** Gets all custom rooms a user is in (excluding system rooms) */
1511
- getUserRooms: (userId: string) => string[];
1512
- /** Checks if a room has any members */
1513
- roomExists: (room: string) => Promise<boolean>;
1514
- /** Emits an event to a specific room */
1515
- emitToRoom: (room: string, event: string, data: unknown) => boolean;
1516
- /** Emits an event to a specific user (all their sockets) */
1517
- emitToUser: (userId: string, event: string, data: unknown) => boolean;
1518
- /** Emits an event to all users with a specific role */
1519
- emitToRole: (roleId: string, event: string, data: unknown) => boolean;
1520
- /** Emits an event to all connected sockets */
1521
- emitToAll: (event: string, data: unknown) => boolean;
1522
- /** Emits an event to all authenticated users */
1523
- emitToAuthenticated: (event: string, data: unknown) => boolean;
1524
- /** Broadcasts to a room except specific user(s) */
1525
- broadcastToRoom: (room: string, exceptUserIds: string | string[], event: string, data: unknown) => boolean;
1526
- }
1527
- /**
1528
- * Application manifest with module and plugin registry.
1529
- *
1530
- * Represents either the core nexus-backend manifest or a user project manifest.
1531
- */
1532
- interface AppManifest {
1533
- /** Package name from package.json */
1534
- name: string;
1535
- /** Package version from package.json */
1536
- version: string;
1537
- /** Registered modules in this application */
1538
- modules: ModuleManifest[];
1539
- /** Registered plugins (only present in user manifest) */
1540
- plugins?: PluginManifest[];
1541
- }
1542
- /**
1543
- * Engine API for runtime introspection of modules and plugins.
1544
- *
1545
- * Provides access to the complete registry of loaded components.
1546
- */
1547
- interface ContextEngine {
1548
- /** Gets all registered modules (core + user) */
1549
- getModules: () => ModuleManifest[];
1550
- /** Gets all registered plugins */
1551
- getPlugins: () => PluginManifest[];
1552
- /** Gets CASL subjects defined by a specific module */
1553
- getModuleSubjects: (mod: ModuleManifest) => string[];
1554
- /** Gets all CASL subjects registered across all modules */
1555
- getRegisteredSubjects: () => string[];
1556
- /** Gets modules from nexus-backend core (auth, users, storage, etc.) */
1557
- getCoreModules: () => ModuleManifest[];
1558
- /** Gets modules from user project and plugins */
1559
- getUserModules: () => ModuleManifest[];
1560
- /** Gets the nexus-backend library manifest */
1561
- getCoreManifest: () => AppManifest;
1562
- /** Gets the user project manifest. Returns null in standalone development mode */
1563
- getUserManifest: () => AppManifest | null;
1564
- /** Returns true if running as a library inside a user project */
1565
- hasUserApp: () => boolean;
1566
- }
1567
- /**
1568
- * Generic validation schema interface compatible with Zod.
1569
- *
1570
- * Any object with a parse() method that throws on invalid data works.
1571
- */
1572
- interface ValidationSchema {
1573
- /** Parses and validates data. Throws on validation failure */
1574
- parse: (data: unknown) => unknown;
1575
- }
1576
- /**
1577
- * Schemas for the validation middleware.
1578
- *
1579
- * Validates request body, query parameters, and URL parameters.
1580
- */
1581
- interface ValidateSchemas {
1582
- /** Schema for request body (req.body) */
1583
- body?: ValidationSchema;
1584
- /** Schema for query parameters (req.query) */
1585
- query?: ValidationSchema;
1586
- /** Schema for URL parameters (req.params) */
1587
- params?: ValidationSchema;
1588
- }
1589
- /**
1590
- * Configuration options for rate limiting middleware.
1591
- */
1592
- interface RateLimitOptions {
1593
- /** Time window duration in milliseconds (default: 60000 = 1 minute) */
1594
- windowMs?: number;
1595
- /** Maximum requests allowed per window (default: 100) */
1596
- max?: number;
1597
- /** Error message when rate limit is exceeded */
1598
- message?: string;
1599
- }
1600
- /**
1601
- * Middleware factories available in module context.
1602
- *
1603
- * Provides common Express middlewares for validation, rate limiting, etc.
1604
- */
1605
- interface ModuleMiddlewares {
1606
- /** Creates validation middleware for body/query/params */
1607
- validate: (schemas: ValidateSchemas) => RequestHandler;
1608
- /** Creates rate limiting middleware */
1609
- rateLimit: (options?: RateLimitOptions) => RequestHandler;
1610
- /** Extensible: additional middlewares can be registered */
1611
- [key: string]: RequestHandler | ((...args: any[]) => RequestHandler) | undefined;
1612
- }
1613
- /**
1614
- * Minimal EventEmitter interface compatible with EventEmitter2.
1615
- *
1616
- * Used for inter-module communication and event-driven architecture.
1617
- */
1618
- interface EventEmitterLike {
1619
- /** Emits an event with arguments. Returns true if listeners exist */
1620
- emit: (event: string, ...args: unknown[]) => boolean;
1621
- /** Registers an event listener */
1622
- on: (event: string, listener: (...args: unknown[]) => void) => this;
1623
- /** Removes an event listener */
1624
- off: (event: string, listener: (...args: unknown[]) => void) => this;
1625
- /** Registers a one-time event listener */
1626
- once: (event: string, listener: (...args: unknown[]) => void) => this;
1627
- /** Registers a listener for all events (EventEmitter2 feature) */
1628
- onAny?: (listener: (event: string, ...args: unknown[]) => void) => this;
1629
- }
1630
- /**
1631
- * Error reporter interface for external monitoring services.
1632
- *
1633
- * Compatible with Sentry, GlitchTip, or any error tracking service.
1634
- */
1635
- interface LoggerReporter {
1636
- /** Captures and reports an exception to the monitoring service */
1637
- captureException: (error: Error, context?: Record<string, unknown>) => void;
1638
- }
1639
- /**
1640
- * Filter operators for advanced filtering
1641
- *
1642
- * @example
1643
- * // Simple equality (shorthand)
1644
- * { status: 'active' }
1645
- *
1646
- * // With operators
1647
- * { name: { $contains: 'john' } }
1648
- * { age: { $gte: 18 } }
1649
- * { email: { $startswith: 'admin' } }
1650
- *
1651
- * // Array (IN operator shorthand)
1652
- * { status: ['active', 'pending'] }
1653
- */
1654
- interface FilterOperators {
1655
- /** Equal (default if no operator) */
1656
- $eq?: unknown;
1657
- /** Not equal */
1658
- $ne?: unknown;
1659
- /** Greater than */
1660
- $gt?: number | string | Date;
1661
- /** Greater than or equal */
1662
- $gte?: number | string | Date;
1663
- /** Less than */
1664
- $lt?: number | string | Date;
1665
- /** Less than or equal */
1666
- $lte?: number | string | Date;
1667
- /** Contains (LIKE %value%) */
1668
- $contains?: string;
1669
- /** Starts with (LIKE value%) */
1670
- $startswith?: string;
1671
- /** Ends with (LIKE %value) */
1672
- $endswith?: string;
1673
- /** In array */
1674
- $in?: unknown[];
1675
- /** Not in array */
1676
- $nin?: unknown[];
1677
- /** Is null (true = IS NULL, false = IS NOT NULL) */
1678
- $isnull?: boolean;
1679
- }
1680
- /**
1681
- * Filter value can be:
1682
- * - Primitive (shorthand for $eq)
1683
- * - Array (shorthand for $in)
1684
- * - Object with operators
1685
- */
1686
- type FilterValue = string | number | boolean | null | unknown[] | FilterOperators;
1687
- /**
1688
- * Query parameters for entity list operations
1689
- *
1690
- * @example
1691
- * // Simple filters
1692
- * { filters: { status: 'active' } }
1693
- *
1694
- * // With operators (see FilterOperators)
1695
- * { filters: { name: { $contains: 'john' }, age: { $gte: 18 } } }
1696
- */
1697
- interface EntityQuery {
1698
- page?: number;
1699
- limit?: number;
1700
- sort?: string;
1701
- order?: 'asc' | 'desc';
1702
- search?: string;
1703
- /** Filters with optional operators - use FilterValue type for type safety */
1704
- filters?: Record<string, unknown>;
1705
- }
1706
- /**
1707
- * Role management service interface.
1708
- *
1709
- * Provides CRUD operations for roles. Used internally by UsersService.
1710
- */
1711
- interface BaseRolesService {
1712
- /** Gets paginated list of all roles */
1713
- findAll(query?: EntityQuery): Promise<PaginatedResult<unknown>>;
1714
- /** Gets a role by its ID */
1715
- findById(id: string): Promise<unknown>;
1716
- /** Gets a role by its name (e.g., 'ADMIN', 'EDITOR') */
1717
- findByName(name: string): Promise<unknown | undefined>;
1718
- /** Creates a new role */
1719
- create(data: Record<string, unknown>, userId?: string): Promise<unknown>;
1720
- /** Updates an existing role */
1721
- update(id: string, data: Record<string, unknown>, userId?: string): Promise<unknown>;
1722
- /** Deletes a role by ID */
1723
- delete(id: string): Promise<void>;
1724
- }
1725
- /**
1726
- * User management service interface.
1727
- *
1728
- * Provides user CRUD and role assignment operations.
1729
- * The backend extends this with concrete types (UserWithRole, etc.).
1730
- */
1731
- interface BaseUsersService {
1732
- /** Gets paginated list of users */
1733
- findAll(query?: EntityQuery): Promise<PaginatedResult<unknown>>;
1734
- /** Gets a user by ID without role information */
1735
- findById(id: string): Promise<unknown>;
1736
- /** Gets a user by ID including their assigned roles */
1737
- findByIdWithRoles(id: string): Promise<unknown | null>;
1738
- /** Creates a new user */
1739
- create(data: Record<string, unknown>, userId?: string): Promise<unknown>;
1740
- /** Updates an existing user */
1741
- update(id: string, data: Record<string, unknown>, userId?: string): Promise<unknown>;
1742
- /** Deletes a user by ID */
1743
- delete(id: string): Promise<void>;
1744
- /** Gets array of role IDs for a user */
1745
- getRoleIds(userId: string): Promise<string[]>;
1746
- /** Gets array of role names for a user (e.g., ['ADMIN', 'EDITOR']) */
1747
- getRoleNames(userId: string): Promise<string[]>;
1748
- /** Gets full role objects for a user */
1749
- getUserRoles(userId: string): Promise<unknown[]>;
1750
- /** Assigns a single role to a user (adds to existing roles) */
1751
- assignRole(userId: string, roleId: string): Promise<void>;
1752
- /** Removes a single role from a user */
1753
- removeRole(userId: string, roleId: string): Promise<void>;
1754
- /** Replaces all user roles with the provided list */
1755
- setRoles(userId: string, roleIds: string[]): Promise<void>;
1756
- /** Nested roles service for direct role management */
1757
- roles: BaseRolesService;
1758
- }
1759
- /**
1760
- * Email sending options.
1761
- *
1762
- * Supports both simple text emails and rich HTML templates with actions.
1763
- */
1764
- interface SendMailOptions {
1765
- /** Recipient email address(es) */
1766
- to: string | string[];
1767
- /** Email subject line */
1768
- subject: string;
1769
- /** Title for email template (displayed prominently) */
1770
- title?: string;
1771
- /** Plain text message for simple emails */
1772
- message?: string;
1773
- /** Full HTML content (overrides template) */
1774
- html?: string;
1775
- /** Plain text fallback (generated from HTML if not provided) */
1776
- text?: string;
1777
- /** Sender email address (uses default if not provided) */
1778
- from?: string;
1779
- /** Reply-to email address */
1780
- replyTo?: string;
1781
- /** CTA buttons for email template */
1782
- actions?: Array<{
1783
- label: string;
1784
- url: string;
1785
- }>;
1786
- /** Logo URL for email header */
1787
- logoUrl?: string;
1788
- /** File attachments */
1789
- attachments?: Array<{
1790
- filename: string;
1791
- content: Buffer | string;
1792
- contentType?: string;
1793
- }>;
1794
- }
1795
- /**
1796
- * Result from sending an email.
1797
- */
1798
- interface SendMailResult {
1799
- /** Unique message ID from the mail server */
1800
- messageId: string;
1801
- /** Email addresses that accepted the message */
1802
- accepted: string[];
1803
- /** Email addresses that rejected the message */
1804
- rejected: string[];
1805
- }
1806
- /**
1807
- * Email service interface for sending emails.
1808
- */
1809
- interface BaseMailService {
1810
- /** Sends an email. Returns null if mail service is not configured */
1811
- send(options: SendMailOptions): Promise<SendMailResult | null>;
1812
- /** Verifies SMTP connection is working */
1813
- verify(): Promise<boolean>;
1814
- }
1815
- /**
1816
- * Core services available via ctx.services.
1817
- *
1818
- * Modules can extend this with additional services.
1819
- */
1820
- interface CoreServices {
1821
- /** Error logging and reporting service */
1822
- logger?: LoggerReporter;
1823
- /** User management service */
1824
- users?: BaseUsersService;
1825
- /** Email sending service */
1826
- mail?: BaseMailService;
1827
- /** Extensible: additional services registered by modules */
1828
- [key: string]: unknown;
1829
- }
1830
- /**
1831
- * Base interface for all entity services.
1832
- *
1833
- * Provides read operations common to all entity types.
1834
- */
1835
- interface BaseEntityService<T = unknown> {
1836
- /** Gets paginated list of records with optional filtering/sorting */
1837
- findAll(query?: EntityQuery): Promise<PaginatedResult<T>>;
1838
- /** Gets a single record by ID. Returns null if not found */
1839
- findById(id: string): Promise<T | null>;
1840
- /** The entity definition this service operates on */
1841
- readonly definition: EntityDefinition;
1842
- }
1843
- /**
1844
- * Service for Collection entities with full CRUD operations.
1845
- *
1846
- * @see {@link CollectionEntityDefinition}
1847
- */
1848
- interface CollectionEntityService<T = unknown> extends BaseEntityService<T> {
1849
- /** Creates a new record. userId is used for audit fields */
1850
- create(data: Partial<T>, userId?: string): Promise<T>;
1851
- /** Updates an existing record by ID */
1852
- update(id: string, data: Partial<T>, userId?: string): Promise<T>;
1853
- /** Deletes a record by ID (soft delete if configured) */
1854
- delete(id: string): Promise<void>;
1855
- }
1856
- /**
1857
- * Service for Temp entities with automatic TTL expiration.
1858
- *
1859
- * @see {@link TempEntityDefinition}
1860
- */
1861
- interface TempEntityService<T = unknown> extends BaseEntityService<T> {
1862
- /** Creates a temporary record with automatic expiration */
1863
- create(data: Partial<T>): Promise<T>;
1864
- /** Updates a record. Optionally extends TTL from current time */
1865
- update(id: string, data: Partial<T>, extendTtl?: boolean): Promise<T>;
1866
- /** Deletes a record immediately */
1867
- delete(id: string): Promise<void>;
1868
- /** Extends the TTL of a record by additional seconds */
1869
- extendTtl(id: string, additionalSeconds?: number): Promise<T>;
1870
- /** Removes all expired records. Returns count of deleted records */
1871
- cleanup(): Promise<number>;
1872
- /** Gets remaining TTL in seconds. Returns null if expired or not found */
1873
- getRemainingTtl(id: string): Promise<number | null>;
1874
- /** Checks if a record exists and hasn't expired */
1875
- isValid(id: string): Promise<boolean>;
1876
- }
1877
- /**
1878
- * Service for Event entities (append-only audit logs).
1879
- *
1880
- * @see {@link EventEntityDefinition}
1881
- */
1882
- interface EventEntityService<T = unknown> extends BaseEntityService<T> {
1883
- /** Creates a new event record (append-only) */
1884
- create(data: Partial<T>): Promise<T>;
1885
- /** Alias for create - appends a new event */
1886
- append(data: Partial<T>): Promise<T>;
1887
- /** Runs retention policy cleanup. Returns count of deleted records */
1888
- runRetentionCleanup(): Promise<number>;
1889
- /** Queries events within a date range */
1890
- findByDateRange(start: Date, end: Date, query?: EntityQuery): Promise<PaginatedResult<T>>;
1891
- }
1892
- /**
1893
- * Service for Single entities (global configuration).
1894
- *
1895
- * @see {@link SingleEntityDefinition}
1896
- */
1897
- interface SingleEntityService<T = unknown> extends BaseEntityService<T> {
1898
- /** Gets the single record (creates with defaults if not exists) */
1899
- get(): Promise<T | null>;
1900
- /** Updates the single record */
1901
- update(data: Partial<T>, userId?: string): Promise<T>;
1902
- }
1903
- /**
1904
- * Service for Config entities (scoped key-value configuration).
1905
- *
1906
- * @see {@link ConfigEntityDefinition}
1907
- */
1908
- interface ConfigEntityService<T = unknown> extends BaseEntityService<T> {
1909
- /** Gets configuration value by key */
1910
- get(key: string): Promise<T | null>;
1911
- /** Sets configuration value for a key */
1912
- set(key: string, value: unknown, userId?: string): Promise<T>;
1913
- /** Gets all configuration as key-value object */
1914
- getAll(): Promise<Record<string, unknown>>;
1915
- }
1916
- /**
1917
- * Service for Reference entities (read-only catalogs).
1918
- * @see {@link ReferenceEntityDefinition}
1919
- */
1920
- type ReferenceEntityService<T = unknown> = BaseEntityService<T>;
1921
- /**
1922
- * Service for View entities (SQL views, read-only).
1923
- * @see {@link ViewEntityDefinition}
1924
- */
1925
- type ViewEntityService<T = unknown> = BaseEntityService<T>;
1926
- /**
1927
- * Service for Computed entities (dynamically calculated data).
1928
- * @see {@link ComputedEntityDefinition}
1929
- */
1930
- type ComputedEntityService<T = unknown> = BaseEntityService<T>;
1931
- /**
1932
- * Service for External entities (external API data).
1933
- * @see {@link ExternalEntityDefinition}
1934
- */
1935
- type ExternalEntityService<T = unknown> = BaseEntityService<T>;
1936
- /**
1937
- * Service for Virtual entities (aggregated from multiple sources).
1938
- * @see {@link VirtualEntityDefinition}
1939
- */
1940
- type VirtualEntityService<T = unknown> = BaseEntityService<T>;
1941
- /**
1942
- * Service for Action entities (command execution).
1943
- *
1944
- * @see {@link ActionEntityDefinition}
1945
- */
1946
- interface ActionEntityService<T = unknown> {
1947
- /** Executes the action with provided input data */
1948
- execute(data: Partial<T>): Promise<unknown>;
1949
- /** The action definition this service implements */
1950
- readonly definition: EntityDefinition;
1951
- }
1952
- /**
1953
- * Hooks to customize entity service behavior.
1954
- * Allows plugins to add logic without inheritance.
1955
- */
1956
- interface EntityServiceHooks<T = unknown> {
1957
- /**
1958
- * Runs before creating a record.
1959
- * @param data Data to insert
1960
- * @returns Modified data
1961
- */
1962
- beforeCreate?: (data: Partial<T>) => Promise<Partial<T>> | Partial<T>;
1963
- /**
1964
- * Runs after creating a record.
1965
- * @param record Created record
1966
- */
1967
- afterCreate?: (record: T) => Promise<void> | void;
1968
- /**
1969
- * Runs before updating a record.
1970
- * @param id Record ID
1971
- * @param data Data to update
1972
- * @returns Modified data
1973
- */
1974
- beforeUpdate?: (id: string, data: Partial<T>) => Promise<Partial<T>> | Partial<T>;
1975
- /**
1976
- * Runs after updating a record.
1977
- * @param record Updated record
1978
- */
1979
- afterUpdate?: (record: T) => Promise<void> | void;
1980
- /**
1981
- * Runs before deleting a record.
1982
- * @param id Record ID
1983
- */
1984
- beforeDelete?: (id: string) => Promise<void> | void;
1985
- /**
1986
- * Runs after deleting a record.
1987
- * @param id Deleted ID
1988
- */
1989
- afterDelete?: (id: string) => Promise<void> | void;
1990
- /**
1991
- * Runs after getting a record by ID.
1992
- * @param record Retrieved record (or null)
1993
- * @returns Modified record
1994
- */
1995
- afterFindById?: (record: T | null) => Promise<T | null> | T | null;
1996
- /**
1997
- * Runs after retrieving a paginated list.
1998
- * @param result Paginated result
1999
- * @returns Modified result
2000
- */
2001
- afterFindAll?: (result: PaginatedResult<T>) => Promise<PaginatedResult<T>> | PaginatedResult<T>;
2002
- }
2003
- /**
2004
- * Options for createEntityService
2005
- */
2006
- interface CreateEntityServiceOptions<T = unknown> {
2007
- /** Hooks to customize behavior */
2008
- hooks?: EntityServiceHooks<T>;
2009
- }
2010
- /**
2011
- * Express route handler type for entity operations.
2012
- */
2013
- type EntityHandler = (req: Request, res: Response) => Promise<void>;
2014
- /**
2015
- * Entity controller with CRUD operation handlers.
2016
- *
2017
- * Generated by `createEntityController` with built-in CASL authorization.
2018
- */
2019
- interface EntityController {
2020
- /** GET / - List all records with pagination */
2021
- list: EntityHandler;
2022
- /** GET /:id - Get single record by ID */
2023
- get: EntityHandler;
2024
- /** POST / - Create new record (collection entities) */
2025
- create?: EntityHandler;
2026
- /** PUT /:id - Update existing record */
2027
- update?: EntityHandler;
2028
- /** DELETE /:id - Delete record */
2029
- delete?: EntityHandler;
2030
- /** POST /:id/:action - Execute entity action */
2031
- execute?: EntityHandler;
2032
- }
2033
- /**
2034
- * Module context providing access to all Nexus services and utilities.
2035
- *
2036
- * Injected into module lifecycle functions (init, routes, migrate, seed).
2037
- *
2038
- * @example
2039
- * ```typescript
2040
- * const myModule: ModuleManifest = {
2041
- * name: 'my-module',
2042
- * routes: (ctx) => {
2043
- * const router = ctx.createRouter()
2044
- * router.get('/hello', (req, res) => res.json({ message: 'Hello!' }))
2045
- * return router
2046
- * }
2047
- * }
2048
- * ```
2049
- */
2050
- interface ModuleContext {
2051
- /** Knex database connection for queries and transactions */
2052
- db: Knex;
2053
- /** Pino logger instance for structured logging */
2054
- logger: Logger;
2055
- /** Helper utilities for migrations and common operations */
2056
- helpers: ContextHelpers;
2057
- /** Cryptographic utilities (password hashing) */
2058
- crypto: ContextCrypto;
2059
- /** Socket.IO API for real-time communication */
2060
- socket: ContextSocket;
2061
- /** Engine API for module/plugin introspection */
2062
- engine: ContextEngine;
2063
- /** Factory for creating Express routers */
2064
- createRouter: () => Router;
2065
- /** Middleware factories (validate, rateLimit, etc.) */
2066
- middleware: ModuleMiddlewares;
2067
- /** Resolved application configuration from env and config files */
2068
- config: Record<string, unknown>;
2069
- /** HTTP error constructors for consistent error responses */
2070
- errors: {
2071
- /** Generic application error with status code */
2072
- AppError: new (message: string, statusCode?: number, details?: unknown) => Error & {
2073
- details?: unknown;
2074
- };
2075
- /** 404 Not Found error */
2076
- NotFoundError: new (message?: string) => Error;
2077
- /** 401 Unauthorized error */
2078
- UnauthorizedError: new (message?: string) => Error;
2079
- /** 403 Forbidden error */
2080
- ForbiddenError: new (message?: string) => Error;
2081
- /** 409 Conflict error */
2082
- ConflictError: new (message?: string) => Error;
2083
- /** 400 Validation error with field details */
2084
- ValidationError: new (message?: string, details?: ValidationDetail[]) => Error & {
2085
- details: ValidationDetail[];
2086
- };
2087
- };
2088
- /** CASL abilities API for authorization */
2089
- abilities: ModuleAbilities;
2090
- /** Event emitter for inter-module communication */
2091
- events: EventEmitterLike;
2092
- /** Registers a service in the internal registry */
2093
- registerService(name: string, service: unknown): void;
2094
- /** Gets a service from the registry with type safety (throws if not found) */
2095
- getService<T = unknown>(serviceName: string): T;
2096
- /** Gets an optional service from the registry (returns undefined if not found) */
2097
- getOptionalService<T = unknown>(serviceName: string): T | undefined;
2098
- /** Checks if a service is available without throwing */
2099
- hasService(serviceName: string): boolean;
2100
- /** Creates entity service based on definition type (auto-detects) */
2101
- createEntityService<T = unknown>(definition: EntityDefinition, options?: CreateEntityServiceOptions<T>): BaseEntityService<T>;
2102
- /** Creates entity controller with CASL authorization and validation */
2103
- createEntityController(service: BaseEntityService, definition: EntityDefinition): EntityController;
2104
- /** Creates Express router with standard CRUD routes */
2105
- createEntityRouter(controller: EntityController, definition: EntityDefinition): Router;
2106
- }
2107
- /**
2108
- * Module manifest defining a Nexus module.
2109
- *
2110
- * Modules are the core building blocks of Nexus applications.
2111
- * Each module can define entities, routes, migrations, and services.
2112
- *
2113
- * @example
2114
- * ```typescript
2115
- * const postsModule: ModuleManifest = {
2116
- * name: 'posts',
2117
- * label: { en: 'Posts', es: 'Publicaciones' },
2118
- * icon: 'mdi:post',
2119
- * category: 'content',
2120
- * definitions: [postsEntity, categoriesEntity],
2121
- * routes: (ctx) => {
2122
- * const router = ctx.createRouter()
2123
- * // Custom routes here
2124
- * return router
2125
- * }
2126
- * }
2127
- * ```
2128
- */
2129
- interface ModuleManifest {
2130
- /** Unique module identifier (e.g., 'users', 'posts', 'cms') */
2131
- name: string;
2132
- /** Display name for UI (singular). Optional if module belongs to a plugin */
2133
- label?: LocalizedString;
2134
- /** Display name for UI (plural, used in menus and lists) */
2135
- labelPlural?: LocalizedString;
2136
- /** Iconify MDI icon identifier (e.g., 'mdi:account-group') */
2137
- icon?: string;
2138
- /** Module description for documentation */
2139
- description?: LocalizedString;
2140
- /** Module type: 'core' (built-in), 'plugin' (from plugin), 'auth-plugin', or 'custom' */
2141
- type?: 'core' | 'plugin' | 'auth-plugin' | 'custom';
2142
- /** Category for sidebar grouping */
2143
- category: Category;
2144
- /** Other module names that must be loaded before this one */
2145
- dependencies?: string[];
2146
- /** Requirements that must be met to enable this module */
2147
- required?: ModuleRequirements;
2148
- /** Database migration function. Usually auto-generated from entity definitions */
2149
- migrate?: (ctx: ModuleContext) => Promise<void>;
2150
- /** Seed function for initial/reference data */
2151
- seed?: (ctx: ModuleContext) => Promise<void>;
2152
- /** Initialization function called on app startup */
2153
- init?: (ctx: ModuleContext) => void;
2154
- /** Factory function returning Express router with custom routes */
2155
- routes?: (ctx: ModuleContext) => Router;
2156
- /** API route prefix. Default: `/{name}` */
2157
- routePrefix?: string;
2158
- /** Entity definitions - single source of truth for DB, validation, UI, and CASL */
2159
- definitions?: EntityDefinition[];
2160
- }
2161
- /**
2162
- * Available categories for organizing modules in the UI sidebar.
2163
- *
2164
- * @see {@link CATEGORIES} for metadata (labels, icons, order)
2165
- */
2166
- type Category = 'content' | 'data' | 'assets' | 'messaging' | 'jobs' | 'ai' | 'analytics' | 'integrations' | 'commerce' | 'security' | 'legal' | 'settings';
2167
- /**
2168
- * Metadata for a category including display information.
2169
- */
2170
- interface CategoryMeta {
2171
- /** Localized display label */
2172
- label: LocalizedString;
2173
- /** Iconify MDI icon identifier */
2174
- icon: string;
2175
- /** Sort order in sidebar (lower = first) */
2176
- order: number;
2177
- }
2178
- /**
2179
- * Registry of all categories with their metadata.
2180
- *
2181
- * Used by the UI to render the sidebar navigation.
2182
- */
2183
- declare const CATEGORIES: Record<Category, CategoryMeta>;
2184
- /**
2185
- * Categories sorted by their display order for sidebar rendering.
11
+ * API DTOs (serializable versions for frontend consumption)
2186
12
  */
2187
- declare const CATEGORY_ORDER: Category[];
2188
- /**
2189
- * Plugin manifest defining a distributable Nexus plugin package.
2190
- *
2191
- * Plugins bundle one or more modules into a reusable package.
2192
- * Distributed via npm (e.g., @gzl10/nexus-plugin-cms).
2193
- *
2194
- * @example
2195
- * ```typescript
2196
- * const cmsPlugin: PluginManifest = {
2197
- * name: '@gzl10/nexus-plugin-cms',
2198
- * code: 'CMS',
2199
- * label: { en: 'CMS', es: 'CMS' },
2200
- * category: 'content',
2201
- * version: '1.0.0',
2202
- * description: { en: 'Content Management System', es: 'Sistema de Gestión de Contenidos' },
2203
- * modules: [postsModule, categoriesModule, tagsModule]
2204
- * }
2205
- * ```
2206
- */
2207
- interface PluginManifest {
2208
- /** Plugin npm package name (e.g., '@gzl10/nexus-plugin-cms') */
2209
- name: string;
2210
- /** Short code prefix for tables (3-4 uppercase chars, e.g., 'CMS'). Applied to all modules */
2211
- code: string;
2212
- /** Display label (singular). Inherited by modules without explicit label */
2213
- label: LocalizedString;
2214
- /** Display label (plural, for menus) */
2215
- labelPlural?: LocalizedString;
2216
- /** Iconify MDI icon. Inherited by modules without explicit icon */
2217
- icon?: string;
2218
- /** Category for sidebar grouping. Inherited by modules without explicit category */
2219
- category: Category;
2220
- /** Plugin version following SemVer (e.g., '1.0.0') */
2221
- version: string;
2222
- /** Plugin description for documentation */
2223
- description: LocalizedString;
2224
- /** Modules bundled in this plugin */
2225
- modules: ModuleManifest[];
2226
- }
13
+
2227
14
  /**
2228
15
  * Serializable field definition for API responses.
2229
- *
2230
- * Unlike {@link FieldDefinition}, this contains no functions (ConditionalBoolean
2231
- * resolved to static boolean). Some fields may be omitted for security.
2232
- *
2233
- * @see {@link FieldDefinition} for the full server-side definition
2234
16
  */
2235
17
  interface FieldDefinitionDTO {
2236
- /** Field identifier */
2237
18
  name: string;
2238
- /** Display label */
2239
19
  label: LocalizedString;
2240
- /** Input type for form rendering */
2241
20
  input: InputType;
2242
- /** Placeholder text */
2243
21
  placeholder?: LocalizedString;
2244
- /** Help text */
2245
22
  hint?: LocalizedString;
2246
- /** Whether field is hidden (resolved from ConditionalBoolean) */
2247
- hidden?: boolean;
2248
- /** Whether field is disabled (resolved from ConditionalBoolean) */
2249
- disabled?: boolean;
2250
- /** Whether field is required (resolved from ConditionalBoolean) */
2251
- required?: boolean;
2252
- /** Default value for form creation */
23
+ hidden?: boolean | FieldCondition;
24
+ disabled?: boolean | FieldCondition;
25
+ required?: boolean | FieldCondition;
2253
26
  defaultValue?: unknown;
2254
- /** Database config (may be partially omitted for security) */
2255
27
  db?: {
2256
28
  type: string;
2257
29
  nullable?: boolean;
@@ -2259,14 +31,12 @@ interface FieldDefinitionDTO {
2259
31
  unique?: boolean;
2260
32
  index?: boolean;
2261
33
  };
2262
- /** Relationship config (table name may be omitted for security) */
2263
34
  relation?: {
2264
35
  table?: string;
2265
36
  column?: string;
2266
37
  labelField?: string;
2267
38
  onDelete?: string;
2268
39
  };
2269
- /** Validation rules */
2270
40
  validation?: {
2271
41
  min?: number;
2272
42
  max?: number;
@@ -2274,7 +44,6 @@ interface FieldDefinitionDTO {
2274
44
  format?: string;
2275
45
  enum?: string[];
2276
46
  };
2277
- /** Select/dropdown options configuration */
2278
47
  options?: {
2279
48
  endpoint?: string;
2280
49
  valueField?: string;
@@ -2282,10 +51,10 @@ interface FieldDefinitionDTO {
2282
51
  static?: Array<{
2283
52
  value: string;
2284
53
  label: LocalizedString;
54
+ icon?: string;
2285
55
  }>;
2286
56
  allowCreate?: boolean;
2287
57
  };
2288
- /** Storage configuration for file uploads */
2289
58
  storage?: {
2290
59
  accept?: string;
2291
60
  maxSize?: number;
@@ -2296,145 +65,461 @@ interface FieldDefinitionDTO {
2296
65
  height: number;
2297
66
  }>;
2298
67
  dedupe?: boolean;
68
+ isPublic?: boolean;
2299
69
  };
2300
- /** Field metadata */
2301
70
  meta?: FieldMeta;
2302
- /** Props for input components */
2303
71
  inputProps?: Record<string, unknown>;
2304
- /** Props for display components */
2305
72
  displayProps?: Record<string, unknown>;
2306
73
  }
74
+ /**
75
+ * Serializable action definition for API responses.
76
+ */
77
+ interface ActionDefinitionDTO {
78
+ key: string;
79
+ label: LocalizedString;
80
+ icon?: string;
81
+ scope?: 'module' | 'entity' | 'row';
82
+ method?: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
83
+ fields?: Record<string, FieldDefinitionDTO>;
84
+ order?: number;
85
+ /** Whether this is a batch action with SSE progress */
86
+ batch?: boolean;
87
+ /** Timeout in ms for batch actions */
88
+ batchTimeout?: number;
89
+ /** Whether to show action result in a modal */
90
+ showResult?: boolean;
91
+ }
2307
92
  /**
2308
93
  * Serializable entity definition for API responses.
2309
- *
2310
- * Flattened structure without discriminated union.
2311
- * Sensitive information may be omitted for security.
2312
- *
2313
- * @see {@link EntityDefinition} for the full server-side definition
2314
94
  */
2315
95
  interface EntityDefinitionDTO {
2316
- /** Unique entity identifier (auto-generated at registration) */
2317
96
  id: string;
2318
- /** Table name (may be omitted for security) */
2319
97
  table?: string;
2320
- /** Key for single entities */
2321
98
  key?: string;
2322
- /** Entity type */
2323
99
  type: EntityType;
2324
- /** Display label (singular) */
2325
100
  label: LocalizedString;
2326
- /** Display label (plural) */
2327
101
  labelPlural?: LocalizedString;
2328
- /** Field used as display label */
2329
102
  labelField?: string;
2330
- /** Iconify icon */
2331
103
  icon?: string;
2332
- /** API route prefix */
2333
104
  routePrefix?: string;
2334
- /** UI sort order */
2335
105
  order?: number;
2336
- /** Field definitions */
2337
106
  fields: Record<string, FieldDefinitionDTO>;
2338
- /** Total field count */
2339
107
  fieldsCount: number;
2340
- /** Has created_at/updated_at columns */
2341
108
  hasTimestamps: boolean;
2342
- /** Has created_by/updated_by columns */
2343
109
  hasAudit: boolean;
2344
- /** CASL subject name */
2345
110
  caslSubject?: string;
2346
- /** Returns single record instead of array */
2347
111
  isSingle?: boolean;
2348
- /** UI display mode */
2349
112
  displayMode?: DisplayMode;
2350
- /** Show create button in UI */
113
+ defaultDisplayMode?: DisplayMode;
114
+ availableDisplayModes?: DisplayMode[];
115
+ hasSearchableFields?: boolean;
116
+ hasSortableFields?: boolean;
117
+ sortableFieldOptions?: Array<{
118
+ value: string;
119
+ label: string;
120
+ }>;
121
+ defaultSort?: {
122
+ field: string;
123
+ order: 'asc' | 'desc';
124
+ };
2351
125
  allowCreate?: boolean;
2352
- /** Allow editing in UI */
2353
126
  allowEdit?: boolean;
2354
- /** Hidden from UI sidebar */
127
+ allowDelete?: boolean;
2355
128
  hidden?: boolean;
129
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
130
+ /** Whether this standalone action entity is a batch action with SSE progress */
131
+ batch?: boolean;
132
+ /** Timeout in ms for batch standalone action entities */
133
+ batchTimeout?: number;
134
+ actions?: ActionDefinitionDTO[];
2356
135
  }
2357
136
  /**
2358
137
  * Serializable module for API responses.
2359
138
  */
2360
139
  interface ModuleDTO {
2361
- /** Module identifier */
2362
140
  name: string;
2363
- /** Display label */
2364
141
  label: LocalizedString;
2365
- /** Display label (plural) */
2366
142
  labelPlural?: LocalizedString;
2367
- /** Iconify icon */
2368
143
  icon?: string;
2369
- /** Module description */
2370
144
  description?: LocalizedString;
2371
- /** Module type */
2372
145
  type: string;
2373
- /** Sidebar category */
2374
146
  category?: Category;
2375
- /** Module dependencies */
2376
147
  dependencies: string[];
2377
- /** API route prefix */
2378
148
  routePrefix: string;
2379
- /** CASL subjects defined by this module */
2380
149
  subjects: string[];
2381
- /** Entity definitions */
2382
150
  definitions: EntityDefinitionDTO[];
2383
- /** Has custom routes */
2384
151
  hasRoutes: boolean;
2385
- /** Has migrations */
2386
152
  hasMigrate: boolean;
2387
- /** Has seed data */
2388
153
  hasSeed: boolean;
2389
- /** Has init function */
2390
154
  hasInit: boolean;
2391
155
  }
156
+ /**
157
+ * Setup information DTO (without postInstall function).
158
+ */
159
+ interface PluginSetupInfoDTO {
160
+ steps?: LocalizedString[];
161
+ docsUrl?: string;
162
+ prerequisites?: LocalizedString[];
163
+ caveats?: LocalizedString[];
164
+ }
2392
165
  /**
2393
166
  * Serializable plugin for API responses.
2394
167
  */
2395
168
  interface PluginDTO {
2396
- /** Plugin package name */
2397
169
  name: string;
2398
- /** Plugin code prefix */
2399
170
  code: string;
2400
- /** Display label */
2401
171
  label: LocalizedString;
2402
- /** Display label (plural) */
2403
172
  labelPlural?: LocalizedString;
2404
- /** Iconify icon */
2405
173
  icon?: string;
2406
- /** Sidebar category */
2407
174
  category?: Category;
2408
- /** Plugin version */
2409
175
  version: string;
2410
- /** Plugin description */
2411
176
  description: LocalizedString;
2412
- /** Modules in this plugin */
2413
177
  modules: ModuleDTO[];
178
+ envVars?: Array<{
179
+ name: string;
180
+ description: LocalizedString;
181
+ required: boolean;
182
+ default?: string;
183
+ example?: string;
184
+ pattern?: string;
185
+ sensitive?: boolean;
186
+ }>;
187
+ peerDependencies?: Record<string, string>;
188
+ setup?: PluginSetupInfoDTO;
189
+ }
190
+ /**
191
+ * Plugin state persisted in database
192
+ */
193
+ interface PluginStateDTO {
194
+ name: string;
195
+ enabled: boolean;
196
+ installed_at: string;
197
+ enabled_at: string | null;
198
+ disabled_at: string | null;
199
+ installed_version: string;
200
+ installed_by: string | null;
201
+ config: Record<string, unknown>;
202
+ }
203
+ /**
204
+ * Plugin available in npm registry (for store)
205
+ */
206
+ interface PluginStoreItemDTO {
207
+ name: string;
208
+ version: string;
209
+ description: string;
210
+ keywords: string[];
211
+ repository: string | null;
212
+ compatibility: 'compatible' | 'incompatible' | 'unknown';
213
+ installed: boolean;
214
+ installedVersion: string | null;
215
+ enabled: boolean | null;
216
+ }
217
+ /**
218
+ * Result of install/uninstall action
219
+ */
220
+ interface PluginActionResult {
221
+ success: boolean;
222
+ message: string;
223
+ restarting?: boolean;
2414
224
  }
2415
225
  /**
2416
226
  * Application manifest DTO (core or user app).
2417
227
  */
2418
228
  interface ManifestDTO {
2419
- /** Application name */
2420
229
  name: string;
2421
- /** Application version */
2422
230
  version: string;
2423
- /** Registered modules */
2424
231
  modules: ModuleDTO[];
2425
- /** Registered plugins */
2426
232
  plugins?: PluginDTO[];
2427
233
  }
2428
234
  /**
2429
235
  * Combined manifest DTO with both core and user manifests.
2430
236
  */
2431
237
  interface CombinedManifestDTO {
2432
- /** Core nexus-backend manifest */
2433
238
  core: ManifestDTO;
2434
- /** User project manifest (null in standalone mode) */
2435
239
  user: ManifestDTO | null;
2436
- /** Whether running with a user project */
2437
240
  hasUserApp: boolean;
2438
241
  }
2439
242
 
2440
- export { type AbilityLike, type ActionEntityDefinition, type ActionEntityService, type AppManifest, type AuthRequest, type BaseAuthRequest, type BaseEntityService, type BaseMailService, type BaseRolesService, type BaseUser, type BaseUsersService, CATEGORIES, CATEGORY_ORDER, type CaslAction, type Category, type CategoryMeta, type CollectionEntityDefinition, type CollectionEntityService, type CombinedManifestDTO, type ComputedEntityDefinition, type ComputedEntityService, type ConditionalBoolean, type ConfigEntityDefinition, type ConfigEntityService, type ContextCrypto, type ContextEngine, type ContextHelpers, type ContextSocket, type CoreServices, type CreateEntityServiceOptions, type DbType, type DisplayMode, type EntityAction, type EntityCaslConfig, type EntityController, type EntityDefinition, type EntityDefinitionDTO, type EntityHandler, type EntityIndex, type EntityQuery, type EntityServiceHooks, type EntityType, type EventEmitterLike, type EventEntityDefinition, type EventEntityService, type ExternalEntityDefinition, type ExternalEntityService, type FieldDbConfig, type FieldDefinition, type FieldDefinitionDTO, type FieldMeta, type FieldOptions, type FieldRelation, type FieldStorageConfig, type FieldValidationConfig, type FilterOperators, type FilterValue, type ForbiddenErrorConstructor, type ForbiddenErrorInstance, type InputType, type KnexAlterTableBuilder, type KnexCreateTableBuilder, type KnexTransaction, type LocalizedString, type LoggerReporter, type ManifestDTO, type ModuleAbilities, type ModuleContext, type ModuleDTO, type ModuleManifest, type ModuleMiddlewares, type ModuleRequirements, type NonPersistentEntityDefinition, type OwnershipCondition, type PaginatedResult, type PaginationParams, type PersistentEntityDefinition, type PluginDTO, type PluginManifest, type RateLimitOptions, type ReferenceEntityDefinition, type ReferenceEntityService, type RolePermission, type SendMailOptions, type SendMailResult, type SingleEntityDefinition, type SingleEntityService, type SocketIOBroadcastOperator, type SocketIOServer, type TempEntityDefinition, type TempEntityService, type ValidateSchemas, type ValidationDetail, type ValidationSchema, type ViewEntityDefinition, type ViewEntityService, type VirtualEntityDefinition, type VirtualEntityService, getCountLabel, getEntityName, getEntitySubject, hasTable, isPersistentEntity, isSingletonEntity, resolveLocalized };
243
+ /**
244
+ * Type guards and utilities for EntityDefinitions
245
+ */
246
+
247
+ /**
248
+ * Entities that persist in the local DB with their own table.
249
+ * Excludes: action, external, virtual, computed, single (uses shared table).
250
+ */
251
+ type PersistentEntityDefinition = CollectionEntityDefinition | ReferenceEntityDefinition | EventEntityDefinition | ConfigEntityDefinition | TempEntityDefinition | ViewEntityDefinition | TreeEntityDefinition | DagEntityDefinition;
252
+ /**
253
+ * Entities without local persistence (read-only or without DB)
254
+ */
255
+ type NonPersistentEntityDefinition = (ActionDefinition & {
256
+ type: 'action';
257
+ }) | ExternalEntityDefinition | VirtualEntityDefinition | ComputedEntityDefinition;
258
+ /**
259
+ * Type guard to check whether an entity persists in the local DB with its own table
260
+ */
261
+ declare function isPersistentEntity(entity: EntityDefinition): entity is PersistentEntityDefinition;
262
+ /**
263
+ * Type guard to check whether it is a singleton (uses shared sys_settings table)
264
+ */
265
+ declare function isSingletonEntity(entity: EntityDefinition): entity is SingleEntityDefinition;
266
+ /**
267
+ * Type guard to check whether an entity has its own table (for migrations)
268
+ */
269
+ declare function hasTable(entity: EntityDefinition): entity is PersistentEntityDefinition;
270
+ /**
271
+ * Gets an entity name in singular PascalCase.
272
+ * 'cms_posts' → 'Post', 'rol_role_permissions' → 'RolePermission'
273
+ */
274
+ declare function getEntityName(entity: EntityDefinition): string;
275
+ /**
276
+ * Gets the CASL subject for an entity
277
+ */
278
+ declare function getEntitySubject(entity: PersistentEntityDefinition): string;
279
+
280
+ /**
281
+ * OIDC Types for Authentication Plugins
282
+ *
283
+ * Shared types for OpenID Connect authentication providers.
284
+ * Used by plugins like @gzl10/nexus-plugin-pocketid and @gzl10/nexus-plugin-google-auth.
285
+ */
286
+ /**
287
+ * OIDC Discovery Document
288
+ * @see https://openid.net/specs/openid-connect-discovery-1_0.html
289
+ */
290
+ interface OidcDiscoveryDocument {
291
+ issuer: string;
292
+ authorization_endpoint: string;
293
+ token_endpoint: string;
294
+ userinfo_endpoint: string;
295
+ jwks_uri: string;
296
+ end_session_endpoint?: string;
297
+ scopes_supported?: string[];
298
+ response_types_supported?: string[];
299
+ grant_types_supported?: string[];
300
+ subject_types_supported?: string[];
301
+ id_token_signing_alg_values_supported?: string[];
302
+ }
303
+ /**
304
+ * OIDC Tokens from token endpoint
305
+ */
306
+ interface OidcTokens {
307
+ access_token: string;
308
+ token_type: string;
309
+ expires_in?: number;
310
+ refresh_token?: string;
311
+ id_token?: string;
312
+ scope?: string;
313
+ }
314
+ /**
315
+ * ID Token claims after validation
316
+ */
317
+ interface IdTokenClaims {
318
+ iss: string;
319
+ sub: string;
320
+ aud: string | string[];
321
+ exp: number;
322
+ iat: number;
323
+ nonce?: string;
324
+ auth_time?: number;
325
+ azp?: string;
326
+ at_hash?: string;
327
+ email?: string;
328
+ email_verified?: boolean;
329
+ name?: string;
330
+ /** Google Workspace hosted domain */
331
+ hd?: string;
332
+ }
333
+ /**
334
+ * OIDC UserInfo response
335
+ */
336
+ interface OidcUserInfo {
337
+ sub: string;
338
+ name?: string;
339
+ given_name?: string;
340
+ family_name?: string;
341
+ preferred_username?: string;
342
+ email?: string;
343
+ email_verified?: boolean;
344
+ picture?: string;
345
+ locale?: string;
346
+ updated_at?: number;
347
+ }
348
+ /**
349
+ * Authorization URL parameters
350
+ */
351
+ interface AuthorizationParams {
352
+ clientId: string;
353
+ redirectUri: string;
354
+ scopes: string[];
355
+ state: string;
356
+ nonce: string;
357
+ responseType?: string;
358
+ /** Google Workspace hosted domain (hd parameter) */
359
+ hostedDomain?: string;
360
+ }
361
+ /**
362
+ * Token exchange parameters
363
+ */
364
+ interface TokenExchangeParams {
365
+ tokenEndpoint: string;
366
+ clientId: string;
367
+ clientSecret: string;
368
+ code: string;
369
+ redirectUri: string;
370
+ }
371
+ /**
372
+ * ID Token validation config
373
+ */
374
+ interface TokenValidationConfig {
375
+ jwksUri: string;
376
+ issuer: string;
377
+ clientId: string;
378
+ nonce?: string;
379
+ }
380
+ /**
381
+ * OIDC state stored temporarily during auth flow
382
+ */
383
+ interface OidcState {
384
+ state: string;
385
+ nonce: string;
386
+ redirectUri: string;
387
+ createdAt: number;
388
+ /** User ID if linking existing account */
389
+ linkUserId?: string;
390
+ }
391
+ /**
392
+ * Session result after successful authentication
393
+ */
394
+ interface SessionResult {
395
+ accessToken: string;
396
+ refreshToken: string;
397
+ expiresIn: number;
398
+ user: {
399
+ id: string;
400
+ email: string;
401
+ name?: string;
402
+ };
403
+ }
404
+
405
+ /**
406
+ * OIDC Client interface
407
+ */
408
+ interface OidcClient {
409
+ /** Discover OIDC endpoints from issuer */
410
+ discover(issuerUrl: string): Promise<OidcDiscoveryDocument>;
411
+ /** Build authorization URL for redirect */
412
+ buildAuthorizationUrl(authorizationEndpoint: string, params: AuthorizationParams): string;
413
+ /** Exchange authorization code for tokens */
414
+ exchangeCode(params: TokenExchangeParams): Promise<OidcTokens>;
415
+ /** Get user info from access token */
416
+ getUserInfo(accessToken: string, userInfoEndpoint: string): Promise<OidcUserInfo>;
417
+ /** Validate ID token using JWKS */
418
+ validateIdToken(idToken: string, config: TokenValidationConfig): Promise<IdTokenClaims>;
419
+ }
420
+ /**
421
+ * Create OIDC client instance
422
+ */
423
+ declare function createOidcClient(): OidcClient;
424
+ /**
425
+ * Get shared OIDC client instance (singleton)
426
+ */
427
+ declare function getOidcClient(): OidcClient;
428
+
429
+ /**
430
+ * OIDC State Manager
431
+ *
432
+ * Manages OIDC state/nonce storage with TTL expiration.
433
+ * Used for CSRF protection during authentication flows.
434
+ *
435
+ * Note: In-memory implementation. For production with multiple
436
+ * instances, consider Redis or database storage.
437
+ */
438
+
439
+ /**
440
+ * State manager interface
441
+ */
442
+ interface StateManager<T extends OidcState = OidcState> {
443
+ /** Store state data */
444
+ store(state: string, data: T): void;
445
+ /** Verify and retrieve state (returns null if expired/invalid) */
446
+ verify(state: string): T | null;
447
+ /** Clear state after use */
448
+ clear(state: string): void;
449
+ /** Get TTL in milliseconds */
450
+ getTtl(): number;
451
+ }
452
+ /**
453
+ * Create in-memory state manager
454
+ *
455
+ * @param ttlMs - Time-to-live in milliseconds (default: 10 minutes)
456
+ */
457
+ declare function createStateManager<T extends OidcState = OidcState>(ttlMs?: number): StateManager<T>;
458
+
459
+ /**
460
+ * Domain Filter for OIDC Authentication
461
+ *
462
+ * Validates email domains against an allowlist.
463
+ * Used to restrict authentication to specific domains.
464
+ */
465
+ /**
466
+ * Domain filter options
467
+ */
468
+ interface DomainFilterOptions {
469
+ /** Throw error if domain not allowed (default: true) */
470
+ throwOnForbidden?: boolean;
471
+ }
472
+ /**
473
+ * Domain filter result
474
+ */
475
+ interface DomainFilterResult {
476
+ allowed: boolean;
477
+ domain: string;
478
+ }
479
+ /**
480
+ * Check if email domain is in allowed list
481
+ *
482
+ * @param email - User email address
483
+ * @param allowedDomainsJson - JSON array of allowed domains (null = all allowed)
484
+ * @returns Domain filter result
485
+ *
486
+ * @example
487
+ * // Allow specific domains
488
+ * const result = checkAllowedDomain('user@example.com', '["example.com", "test.com"]')
489
+ * // result: { allowed: true, domain: 'example.com' }
490
+ *
491
+ * @example
492
+ * // Allow all domains
493
+ * const result = checkAllowedDomain('user@any.com', null)
494
+ * // result: { allowed: true, domain: 'any.com' }
495
+ */
496
+ declare function checkAllowedDomain(email: string, allowedDomainsJson: string | null): DomainFilterResult;
497
+ /**
498
+ * Assert email domain is allowed (throws if not)
499
+ *
500
+ * @param email - User email address
501
+ * @param allowedDomainsJson - JSON array of allowed domains
502
+ * @param errorClass - Error class to throw (default: Error)
503
+ * @throws Error if domain is not allowed
504
+ */
505
+ declare function assertAllowedDomain(email: string, allowedDomainsJson: string | null, errorClass?: new (message: string) => Error): void;
506
+
507
+ /**
508
+ * @module nexus-sdk
509
+ * @description SDK types for creating Nexus plugins and modules without full backend dependency
510
+ *
511
+ * @gzl10/nexus-sdk
512
+ *
513
+ * Use this package to define plugin/module manifests without
514
+ * depending on the full @gzl10/nexus-backend package.
515
+ *
516
+ * @remarks
517
+ * Types are organized in submodules under ./types/ for maintainability.
518
+ * This barrel file re-exports everything for backwards compatibility.
519
+ */
520
+
521
+ type KnexCreateTableBuilder = Knex.CreateTableBuilder;
522
+ type KnexAlterTableBuilder = Knex.AlterTableBuilder;
523
+ type KnexTransaction = Knex.Transaction;
524
+
525
+ export { ActionDefinition, type ActionDefinitionDTO, type AuthorizationParams, Category, CollectionEntityDefinition, type CombinedManifestDTO, ComputedEntityDefinition, ConfigEntityDefinition, DagEntityDefinition, DisplayMode, type DomainFilterOptions, type DomainFilterResult, EntityDefinition, type EntityDefinitionDTO, EntityType, EventEntityDefinition, ExternalEntityDefinition, FieldCondition, type FieldDefinitionDTO, FieldMeta, type IdTokenClaims, InputType, type KnexAlterTableBuilder, type KnexCreateTableBuilder, type KnexTransaction, LocalizedString, type ManifestDTO, type ModuleDTO, type NonPersistentEntityDefinition, type OidcClient, type OidcDiscoveryDocument, type OidcState, type OidcTokens, type OidcUserInfo, type PersistentEntityDefinition, type PluginActionResult, type PluginDTO, type PluginSetupInfoDTO, type PluginStateDTO, type PluginStoreItemDTO, ReferenceEntityDefinition, type SessionResult, SingleEntityDefinition, type StateManager, TempEntityDefinition, type TokenExchangeParams, type TokenValidationConfig, TreeEntityDefinition, ViewEntityDefinition, VirtualEntityDefinition, assertAllowedDomain, checkAllowedDomain, createOidcClient, createStateManager, getEntityName, getEntitySubject, getOidcClient, hasTable, isPersistentEntity, isSingletonEntity };