@gzl10/nexus-sdk 0.12.3 → 0.12.6
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 +1654 -413
- package/dist/index.js +26 -12
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,37 +5,37 @@ export { CookieOptions, NextFunction, Request, RequestHandler, Response, Router
|
|
|
5
5
|
import { Logger } from 'pino';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Type guards
|
|
8
|
+
* Type guards and utilities for EntityDefinitions
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Entities that persist in the local DB with their own table.
|
|
13
|
+
* Excludes: action, external, virtual, computed, single (uses shared table).
|
|
14
14
|
*/
|
|
15
15
|
type PersistentEntityDefinition = CollectionEntityDefinition | ReferenceEntityDefinition | EventEntityDefinition | ConfigEntityDefinition | TempEntityDefinition | ViewEntityDefinition;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* Entities without local persistence (read-only or without DB)
|
|
18
18
|
*/
|
|
19
19
|
type NonPersistentEntityDefinition = ActionEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition | ComputedEntityDefinition;
|
|
20
20
|
/**
|
|
21
|
-
* Type guard
|
|
21
|
+
* Type guard to check whether an entity persists in the local DB with its own table
|
|
22
22
|
*/
|
|
23
23
|
declare function isPersistentEntity(entity: EntityDefinition): entity is PersistentEntityDefinition;
|
|
24
24
|
/**
|
|
25
|
-
* Type guard
|
|
25
|
+
* Type guard to check whether it is a singleton (uses shared sys_settings table)
|
|
26
26
|
*/
|
|
27
27
|
declare function isSingletonEntity(entity: EntityDefinition): entity is SingleEntityDefinition;
|
|
28
28
|
/**
|
|
29
|
-
* Type guard
|
|
29
|
+
* Type guard to check whether an entity has its own table (for migrations)
|
|
30
30
|
*/
|
|
31
31
|
declare function hasTable(entity: EntityDefinition): entity is PersistentEntityDefinition;
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* Gets an entity name in singular PascalCase.
|
|
34
34
|
* 'cms_posts' → 'Post', 'rol_role_permissions' → 'RolePermission'
|
|
35
35
|
*/
|
|
36
36
|
declare function getEntityName(entity: EntityDefinition): string;
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* Gets the CASL subject for an entity
|
|
39
39
|
*/
|
|
40
40
|
declare function getEntitySubject(entity: PersistentEntityDefinition): string;
|
|
41
41
|
|
|
@@ -47,57 +47,59 @@ declare function getEntitySubject(entity: PersistentEntityDefinition): string;
|
|
|
47
47
|
* depending on the full @gzl10/nexus-backend package.
|
|
48
48
|
*/
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* Discriminated union of all entity types
|
|
51
51
|
*
|
|
52
|
-
* | Type |
|
|
53
|
-
*
|
|
54
|
-
* | collection |
|
|
55
|
-
* | single |
|
|
56
|
-
* | external | No
|
|
57
|
-
* | virtual | No
|
|
58
|
-
* | computed | No/
|
|
59
|
-
* | view |
|
|
60
|
-
* | reference |
|
|
61
|
-
* | config |
|
|
62
|
-
* | event |
|
|
63
|
-
* | temp | No (TTL)
|
|
64
|
-
* | action | No
|
|
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
65
|
*/
|
|
66
66
|
|
|
67
67
|
type KnexCreateTableBuilder = Knex.CreateTableBuilder;
|
|
68
68
|
type KnexAlterTableBuilder = Knex.AlterTableBuilder;
|
|
69
69
|
type KnexTransaction = Knex.Transaction;
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
71
|
+
* Base authenticated request with user and CASL abilities (generic).
|
|
72
|
+
* Use AuthRequest for the pre-typed version.
|
|
73
73
|
*/
|
|
74
|
-
interface
|
|
74
|
+
interface BaseAuthRequest<TUser = unknown, TAbility = unknown> extends Request {
|
|
75
75
|
user: TUser;
|
|
76
76
|
ability: TAbility;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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.
|
|
81
82
|
*/
|
|
82
83
|
interface BaseUser {
|
|
83
84
|
id: string;
|
|
84
85
|
name: string;
|
|
85
86
|
email: string;
|
|
86
|
-
role_id: string;
|
|
87
87
|
created_at: Date;
|
|
88
88
|
updated_at: Date;
|
|
89
89
|
created_by: string | null;
|
|
90
90
|
updated_by: string | null;
|
|
91
|
+
/** Extensibility: allows custom user properties */
|
|
92
|
+
[key: string]: unknown;
|
|
91
93
|
}
|
|
92
94
|
/**
|
|
93
|
-
*
|
|
95
|
+
* Pagination parameters for queries
|
|
94
96
|
*/
|
|
95
97
|
interface PaginationParams {
|
|
96
98
|
page: number;
|
|
97
99
|
limit: number;
|
|
98
100
|
}
|
|
99
101
|
/**
|
|
100
|
-
*
|
|
102
|
+
* Generic paginated result
|
|
101
103
|
*/
|
|
102
104
|
interface PaginatedResult<T> {
|
|
103
105
|
items: T[];
|
|
@@ -108,183 +110,603 @@ interface PaginatedResult<T> {
|
|
|
108
110
|
hasNext: boolean;
|
|
109
111
|
}
|
|
110
112
|
/**
|
|
111
|
-
*
|
|
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
|
|
112
138
|
*/
|
|
113
|
-
|
|
139
|
+
declare function resolveLocalized(value: LocalizedString | undefined, locale: string): string;
|
|
114
140
|
/**
|
|
115
|
-
*
|
|
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
|
|
116
149
|
*/
|
|
117
|
-
|
|
150
|
+
declare function getCountLabel(label: LocalizedString | undefined, labelPlural: LocalizedString | undefined, count: number, locale: string): string;
|
|
118
151
|
/**
|
|
119
|
-
*
|
|
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
|
+
* ```
|
|
120
237
|
*/
|
|
121
238
|
interface FieldDbConfig {
|
|
239
|
+
/** Column type for the database */
|
|
122
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 */
|
|
123
244
|
size?: number;
|
|
245
|
+
/** Precision and scale for DECIMAL: [precision, scale] e.g., [10, 2] for DECIMAL(10,2) */
|
|
124
246
|
precision?: [number, number];
|
|
125
|
-
|
|
247
|
+
/** Allow NULL values. Inferred from `required` if not specified */
|
|
248
|
+
nullable?: boolean;
|
|
249
|
+
/** Create unique constraint on this column */
|
|
126
250
|
unique?: boolean;
|
|
251
|
+
/** Static default value for the column */
|
|
127
252
|
default?: unknown;
|
|
253
|
+
/** Database function for default value: 'now' for timestamps, 'uuid' for auto-generated UUIDs */
|
|
128
254
|
defaultFn?: 'now' | 'uuid';
|
|
255
|
+
/** Create index on this column for faster queries */
|
|
129
256
|
index?: boolean;
|
|
130
257
|
}
|
|
131
258
|
/**
|
|
132
|
-
*
|
|
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
|
+
* ```
|
|
133
275
|
*/
|
|
134
276
|
interface FieldRelation {
|
|
277
|
+
/** Target table name */
|
|
135
278
|
table: string;
|
|
279
|
+
/** Target column name (default: 'id') */
|
|
136
280
|
column?: string;
|
|
281
|
+
/** Action when referenced row is deleted */
|
|
137
282
|
onDelete?: 'CASCADE' | 'RESTRICT' | 'SET NULL' | 'NO ACTION';
|
|
283
|
+
/** Action when referenced row's key is updated */
|
|
138
284
|
onUpdate?: 'CASCADE' | 'RESTRICT' | 'SET NULL' | 'NO ACTION';
|
|
139
285
|
}
|
|
140
286
|
/**
|
|
141
|
-
*
|
|
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
|
+
* ```
|
|
142
306
|
*/
|
|
143
307
|
interface FieldValidationConfig {
|
|
144
|
-
|
|
308
|
+
/** Minimum value (numbers) or minimum length (strings) */
|
|
145
309
|
min?: number;
|
|
310
|
+
/** Maximum value (numbers) or maximum length (strings) */
|
|
146
311
|
max?: number;
|
|
312
|
+
/** Regular expression pattern for validation */
|
|
147
313
|
pattern?: string;
|
|
314
|
+
/** Predefined format validation */
|
|
148
315
|
format?: 'email' | 'url' | 'uuid' | 'slug';
|
|
316
|
+
/** Allowed values (creates enum constraint) */
|
|
149
317
|
enum?: string[];
|
|
150
318
|
}
|
|
151
319
|
/**
|
|
152
|
-
*
|
|
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
|
+
* ```
|
|
153
347
|
*/
|
|
154
348
|
interface FieldOptions {
|
|
349
|
+
/** API endpoint for dynamic options (GET request) */
|
|
155
350
|
endpoint?: string;
|
|
351
|
+
/** Field to use as option value (default: 'id') */
|
|
156
352
|
valueField?: string;
|
|
353
|
+
/** Field to use as option label (default: 'name') */
|
|
157
354
|
labelField?: string;
|
|
355
|
+
/** Static list of options */
|
|
158
356
|
static?: Array<{
|
|
159
357
|
value: string;
|
|
160
|
-
label:
|
|
358
|
+
label: LocalizedString;
|
|
161
359
|
}>;
|
|
360
|
+
/** Allow creating new options on-the-fly (renders combobox) */
|
|
162
361
|
allowCreate?: boolean;
|
|
163
362
|
}
|
|
164
363
|
/**
|
|
165
|
-
*
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
*
|
|
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
|
+
* ```
|
|
175
393
|
*/
|
|
176
394
|
interface FieldStorageConfig {
|
|
177
|
-
/**
|
|
395
|
+
/** Accepted MIME types (e.g., 'image/*', 'application/pdf', 'image/png,image/jpeg') */
|
|
178
396
|
accept?: string;
|
|
179
|
-
/**
|
|
397
|
+
/** Maximum file size in bytes (default: 10MB = 10485760) */
|
|
180
398
|
maxSize?: number;
|
|
181
|
-
/**
|
|
399
|
+
/** Maximum number of files for array types (fileArray, imageArray) */
|
|
182
400
|
maxFiles?: number;
|
|
183
|
-
/**
|
|
401
|
+
/** Folder/prefix in storage bucket (e.g., 'avatars', 'documents/contracts') */
|
|
184
402
|
folder?: string;
|
|
185
|
-
/**
|
|
403
|
+
/** Thumbnail sizes to auto-generate for images. Creates resized versions on upload */
|
|
186
404
|
thumbnails?: Array<{
|
|
187
405
|
width: number;
|
|
188
406
|
height: number;
|
|
189
407
|
}>;
|
|
408
|
+
/** Enable SHA256 hash deduplication - if identical file exists, reuse it instead of uploading again */
|
|
409
|
+
dedupe?: boolean;
|
|
190
410
|
}
|
|
191
411
|
/**
|
|
192
|
-
*
|
|
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
|
+
* ```
|
|
193
430
|
*/
|
|
194
431
|
interface FieldMeta {
|
|
432
|
+
/** Enable sorting by this field in table columns */
|
|
195
433
|
sortable?: boolean;
|
|
434
|
+
/** Include this field in search/filter operations */
|
|
196
435
|
searchable?: boolean;
|
|
436
|
+
/** Include this field in CSV/Excel exports */
|
|
197
437
|
exportable?: boolean;
|
|
438
|
+
/** Mark as audit field (auto-populated with user ID on create/update) */
|
|
198
439
|
auditField?: 'created_by' | 'updated_by';
|
|
199
|
-
/**
|
|
200
|
-
|
|
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';
|
|
201
444
|
}
|
|
202
445
|
/**
|
|
203
|
-
*
|
|
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
|
|
204
458
|
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
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
|
|
209
473
|
*/
|
|
210
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
|
+
*/
|
|
211
480
|
name: string;
|
|
212
|
-
|
|
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
|
+
*/
|
|
213
490
|
input: InputType;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
+
*/
|
|
218
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
|
+
*/
|
|
219
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
|
+
*/
|
|
220
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
|
+
*/
|
|
221
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
|
+
*/
|
|
222
559
|
storage?: FieldStorageConfig;
|
|
560
|
+
/**
|
|
561
|
+
* Additional metadata for categorization and UI organization.
|
|
562
|
+
* @see {@link FieldMeta}
|
|
563
|
+
*/
|
|
223
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>;
|
|
224
577
|
}
|
|
225
578
|
/**
|
|
226
|
-
*
|
|
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
|
+
* ```
|
|
227
591
|
*/
|
|
228
592
|
interface EntityIndex {
|
|
593
|
+
/** Column names to include in the composite index */
|
|
229
594
|
columns: string[];
|
|
595
|
+
/** Create unique constraint on the column combination */
|
|
230
596
|
unique?: boolean;
|
|
231
597
|
}
|
|
232
598
|
/**
|
|
233
|
-
*
|
|
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
|
|
234
610
|
*/
|
|
235
611
|
type CaslAction = 'manage' | 'create' | 'read' | 'update' | 'delete' | 'execute';
|
|
236
612
|
/**
|
|
237
|
-
*
|
|
238
|
-
*
|
|
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
|
+
* ```
|
|
239
628
|
*/
|
|
240
629
|
interface OwnershipCondition {
|
|
241
|
-
/**
|
|
630
|
+
/** Entity field that contains the owner reference (e.g., 'author_id', 'created_by') */
|
|
242
631
|
field: string;
|
|
243
|
-
/**
|
|
632
|
+
/** User property to compare against (default: 'id'). Use 'email' for email-based ownership */
|
|
244
633
|
userField?: string;
|
|
245
634
|
}
|
|
246
635
|
/**
|
|
247
|
-
*
|
|
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
|
+
* ```
|
|
248
661
|
*/
|
|
249
662
|
interface RolePermission {
|
|
250
|
-
/**
|
|
663
|
+
/** Actions this role can perform on the entity */
|
|
251
664
|
actions: CaslAction[];
|
|
252
|
-
/**
|
|
665
|
+
/** CASL conditions to filter which records the permission applies to */
|
|
253
666
|
conditions?: Record<string, unknown>;
|
|
254
|
-
/**
|
|
667
|
+
/** Restrict permission to specific fields. null = all fields allowed */
|
|
255
668
|
fields?: string[] | null;
|
|
256
|
-
/**
|
|
669
|
+
/** If true, this is a denial rule (cannot) instead of allow (can) */
|
|
257
670
|
inverted?: boolean;
|
|
258
671
|
}
|
|
259
672
|
/**
|
|
260
|
-
*
|
|
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
|
|
261
694
|
*/
|
|
262
695
|
interface EntityCaslConfig {
|
|
263
|
-
/**
|
|
264
|
-
* Subject CASL (default: inferido de table → 'cms_posts' → 'CmsPost')
|
|
265
|
-
*/
|
|
696
|
+
/** CASL subject name. Default: inferred from table (e.g., 'cms_posts' → 'CmsPost') */
|
|
266
697
|
subject?: string;
|
|
267
|
-
/**
|
|
268
|
-
* Campo de ownership para conditions automáticas
|
|
269
|
-
* Si se define, se genera condition { [field]: '${user.id}' }
|
|
270
|
-
*/
|
|
698
|
+
/** Ownership configuration for automatic user-based filtering */
|
|
271
699
|
ownership?: OwnershipCondition;
|
|
272
|
-
/**
|
|
273
|
-
* Permisos por rol
|
|
274
|
-
* Las keys son nombres de roles (ADMIN, EDITOR, VIEWER, etc.)
|
|
275
|
-
*/
|
|
700
|
+
/** Role-based permissions. Keys are role names (ADMIN, EDITOR, VIEWER, etc.) */
|
|
276
701
|
permissions?: Record<string, RolePermission>;
|
|
277
|
-
/**
|
|
278
|
-
* Campos sensibles que requieren permiso especial
|
|
279
|
-
* Se excluyen de 'read' para roles sin acceso explícito
|
|
280
|
-
*/
|
|
702
|
+
/** Fields excluded from 'read' permission unless explicitly granted. Use for PII or internal data */
|
|
281
703
|
sensitiveFields?: string[];
|
|
282
704
|
}
|
|
283
705
|
/**
|
|
284
|
-
* Action
|
|
285
|
-
*
|
|
706
|
+
* Action linked to an entity record.
|
|
707
|
+
* Defines operations that act on existing records.
|
|
286
708
|
*
|
|
287
|
-
*
|
|
709
|
+
* Generated route: GET|POST /{entityRoutePrefix}/{key}/:id
|
|
288
710
|
*
|
|
289
711
|
* @example
|
|
290
712
|
* ```typescript
|
|
@@ -296,493 +718,920 @@ interface EntityCaslConfig {
|
|
|
296
718
|
* select: ['id', 'filename', 'mimetype'],
|
|
297
719
|
* handler: async (ctx, input, _req, res) => {
|
|
298
720
|
* const { _record: file, _authUserId } = input
|
|
299
|
-
* // file
|
|
721
|
+
* // file and _authUserId are injected automatically
|
|
300
722
|
* }
|
|
301
723
|
* }]
|
|
302
724
|
* ```
|
|
303
725
|
*/
|
|
304
726
|
interface EntityAction {
|
|
305
|
-
/**
|
|
727
|
+
/** Unique key for the route (e.g., 'download' → /download/:id) */
|
|
306
728
|
key: string;
|
|
307
|
-
/** Label
|
|
308
|
-
label:
|
|
309
|
-
/**
|
|
729
|
+
/** Label for the UI */
|
|
730
|
+
label: LocalizedString;
|
|
731
|
+
/** Iconify icon (default: 'mdi:play') */
|
|
310
732
|
icon?: string;
|
|
311
|
-
/**
|
|
312
|
-
method?: 'GET' | 'POST';
|
|
313
|
-
/**
|
|
733
|
+
/** HTTP method (default: 'POST') */
|
|
734
|
+
method?: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
|
|
735
|
+
/** Fields to select from the record (default: all) */
|
|
314
736
|
select?: string[];
|
|
315
|
-
/**
|
|
737
|
+
/** Body validation schema (optional) */
|
|
316
738
|
inputSchema?: ValidationSchema;
|
|
317
|
-
/**
|
|
739
|
+
/** Output schema (documentation) */
|
|
318
740
|
outputSchema?: ValidationSchema;
|
|
319
|
-
/**
|
|
741
|
+
/** Custom middleware (e.g., multer) */
|
|
320
742
|
middleware?: (ctx: ModuleContext) => RequestHandler | RequestHandler[];
|
|
321
743
|
/**
|
|
322
|
-
* Handler -
|
|
323
|
-
* @param ctx -
|
|
324
|
-
* @param input -
|
|
744
|
+
* Handler - receives _record and _authUserId automatically.
|
|
745
|
+
* @param ctx - Module context
|
|
746
|
+
* @param input - Validated body + _record + _authUserId
|
|
325
747
|
* @param req - Express Request
|
|
326
|
-
* @param res - Express Response (
|
|
748
|
+
* @param res - Express Response (for streams, custom headers)
|
|
327
749
|
*/
|
|
328
750
|
handler: (ctx: ModuleContext, input: unknown, req?: Request, res?: Response) => Promise<unknown>;
|
|
329
751
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
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.
|
|
332
761
|
*/
|
|
333
762
|
casl?: {
|
|
334
|
-
/**
|
|
763
|
+
/** CASL action (default: 'execute') */
|
|
335
764
|
action?: string;
|
|
336
|
-
/**
|
|
765
|
+
/** Additional conditions */
|
|
337
766
|
conditions?: Record<string, unknown>;
|
|
338
767
|
};
|
|
339
768
|
}
|
|
340
769
|
/**
|
|
341
|
-
*
|
|
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}
|
|
342
776
|
*/
|
|
343
777
|
interface BaseEntityDefinition {
|
|
344
|
-
/**
|
|
778
|
+
/** Database table name with module prefix (e.g., 'cms_posts', 'auth_users') */
|
|
345
779
|
table: string;
|
|
346
|
-
/**
|
|
347
|
-
label:
|
|
348
|
-
/**
|
|
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} */
|
|
349
785
|
fields: Record<string, FieldDefinition>;
|
|
350
|
-
/**
|
|
786
|
+
/** CASL authorization configuration for role-based access control */
|
|
351
787
|
casl?: EntityCaslConfig;
|
|
352
|
-
/**
|
|
788
|
+
/** API route prefix. Default: inferred from table name (e.g., 'cms_posts' → '/cms-posts') */
|
|
353
789
|
routePrefix?: string;
|
|
354
|
-
/**
|
|
790
|
+
/** UI sidebar ordering. Lower values appear first (default: 999) */
|
|
355
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;
|
|
356
796
|
}
|
|
357
797
|
/**
|
|
358
|
-
*
|
|
359
|
-
*
|
|
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
|
+
* ```
|
|
360
816
|
*/
|
|
361
817
|
interface CollectionEntityDefinition extends BaseEntityDefinition {
|
|
818
|
+
/** Entity type. Default: 'collection' */
|
|
362
819
|
type?: 'collection';
|
|
363
|
-
/**
|
|
820
|
+
/** Field used as display label in dropdowns, references, and breadcrumbs (e.g., 'name', 'title') */
|
|
364
821
|
labelField: string;
|
|
365
|
-
/**
|
|
822
|
+
/** Auto-add created_at and updated_at timestamp columns */
|
|
366
823
|
timestamps?: boolean;
|
|
367
|
-
/**
|
|
824
|
+
/** Auto-add created_by and updated_by user reference columns */
|
|
368
825
|
audit?: boolean;
|
|
369
|
-
/**
|
|
826
|
+
/** Composite database indexes for query optimization */
|
|
370
827
|
indexes?: EntityIndex[];
|
|
371
|
-
/**
|
|
828
|
+
/** Use soft delete (deleted_at column) instead of physical DELETE */
|
|
372
829
|
softDelete?: boolean;
|
|
373
|
-
/**
|
|
830
|
+
/** Custom actions that operate on entity records (e.g., download, publish, archive) */
|
|
374
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;
|
|
375
836
|
}
|
|
376
837
|
/**
|
|
377
|
-
*
|
|
378
|
-
*
|
|
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.
|
|
379
842
|
*
|
|
380
843
|
* @example
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
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
|
+
* ```
|
|
384
857
|
*/
|
|
385
858
|
interface SingleEntityDefinition {
|
|
859
|
+
/** Entity type identifier */
|
|
386
860
|
type: 'single';
|
|
387
|
-
/**
|
|
861
|
+
/** Unique key in sys_settings table (e.g., 'site_config', 'smtp_settings', 'feature_flags') */
|
|
388
862
|
key: string;
|
|
389
|
-
/**
|
|
390
|
-
label:
|
|
391
|
-
/**
|
|
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 */
|
|
392
868
|
fields: Record<string, FieldDefinition>;
|
|
393
|
-
/**
|
|
869
|
+
/** Default values applied when the setting is first accessed */
|
|
394
870
|
defaults?: Record<string, unknown>;
|
|
395
|
-
/**
|
|
871
|
+
/** CASL authorization for read/update operations */
|
|
396
872
|
casl?: EntityCaslConfig;
|
|
397
|
-
/**
|
|
873
|
+
/** API route prefix. Default: inferred from key */
|
|
398
874
|
routePrefix?: string;
|
|
399
|
-
/**
|
|
875
|
+
/** Custom actions for this singleton (e.g., 'test-smtp', 'reset-defaults') */
|
|
400
876
|
actions?: EntityAction[];
|
|
401
877
|
}
|
|
402
878
|
/**
|
|
403
|
-
*
|
|
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
|
+
* ```
|
|
404
899
|
*/
|
|
405
900
|
interface ReferenceEntityDefinition extends BaseEntityDefinition {
|
|
901
|
+
/** Entity type identifier */
|
|
406
902
|
type: 'reference';
|
|
407
|
-
/**
|
|
903
|
+
/** Field used as display label in dropdowns (e.g., 'name', 'title') */
|
|
408
904
|
labelField: string;
|
|
409
|
-
/**
|
|
905
|
+
/** Auto-add created_at and updated_at timestamp columns */
|
|
410
906
|
timestamps?: boolean;
|
|
411
|
-
/**
|
|
907
|
+
/** Composite database indexes */
|
|
412
908
|
indexes?: EntityIndex[];
|
|
413
|
-
/**
|
|
909
|
+
/** Initial seed data inserted on first migration/startup */
|
|
414
910
|
seed?: Array<Record<string, unknown>>;
|
|
415
|
-
/**
|
|
911
|
+
/** Allow admin users to edit reference data. Default: false (read-only) */
|
|
416
912
|
allowAdminEdit?: boolean;
|
|
417
913
|
}
|
|
418
914
|
/**
|
|
419
|
-
*
|
|
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
|
+
* ```
|
|
420
936
|
*/
|
|
421
937
|
interface EventEntityDefinition extends BaseEntityDefinition {
|
|
938
|
+
/** Entity type identifier */
|
|
422
939
|
type: 'event';
|
|
423
|
-
/**
|
|
940
|
+
/** Field used as display label in event lists (e.g., 'action', 'event_type') */
|
|
424
941
|
labelField: string;
|
|
425
|
-
/**
|
|
942
|
+
/** Auto-add created_at column. Set false if you define timestamp fields explicitly */
|
|
426
943
|
timestamps?: boolean;
|
|
427
|
-
/**
|
|
944
|
+
/** Automatic data retention to limit table growth */
|
|
428
945
|
retention?: {
|
|
429
|
-
/**
|
|
946
|
+
/** Delete records older than N days */
|
|
430
947
|
days?: number;
|
|
431
|
-
/**
|
|
948
|
+
/** Keep only the most recent N records (FIFO) */
|
|
432
949
|
maxRows?: number;
|
|
433
950
|
};
|
|
434
951
|
}
|
|
435
952
|
/**
|
|
436
|
-
*
|
|
437
|
-
*
|
|
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
|
+
* ```
|
|
438
975
|
*/
|
|
439
976
|
interface ActionEntityDefinition {
|
|
977
|
+
/** Entity type identifier */
|
|
440
978
|
type: 'action';
|
|
441
|
-
/**
|
|
442
|
-
label:
|
|
443
|
-
/**
|
|
979
|
+
/** Display name for the action button/form */
|
|
980
|
+
label: LocalizedString;
|
|
981
|
+
/** Iconify icon identifier (default: 'mdi:play') */
|
|
444
982
|
icon?: string;
|
|
445
|
-
/**
|
|
983
|
+
/** Form fields for action input. These don't create database columns */
|
|
446
984
|
fields: Record<string, FieldDefinition>;
|
|
447
|
-
/**
|
|
985
|
+
/** Zod schema for validating input before execution */
|
|
448
986
|
inputSchema?: ValidationSchema;
|
|
449
|
-
/**
|
|
987
|
+
/** Zod schema documenting the response structure */
|
|
450
988
|
outputSchema?: ValidationSchema;
|
|
451
|
-
/**
|
|
452
|
-
method?: 'GET' | 'POST';
|
|
453
|
-
/**
|
|
454
|
-
* Middleware a aplicar antes del handler (ej: multer para uploads)
|
|
455
|
-
* Puede retornar un middleware o un array de middlewares
|
|
456
|
-
*/
|
|
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 */
|
|
457
992
|
middleware?: (ctx: ModuleContext) => RequestHandler | RequestHandler[];
|
|
458
|
-
/**
|
|
459
|
-
* Handler que ejecuta la acción
|
|
460
|
-
* @param ctx - Contexto del módulo
|
|
461
|
-
* @param input - Datos del body. Incluye _authUserId si hay usuario autenticado.
|
|
462
|
-
* @param req - Request de Express (opcional, para acceder a req.file, etc.)
|
|
463
|
-
* @param res - Response de Express (opcional, para streams, headers custom, etc.)
|
|
464
|
-
*/
|
|
993
|
+
/** Handler function that executes the action logic. Input includes _authUserId if authenticated */
|
|
465
994
|
handler?: (ctx: ModuleContext, input: unknown, req?: Request, res?: Response) => Promise<unknown>;
|
|
466
|
-
/** HTTP status code
|
|
995
|
+
/** HTTP status code for success response. Default: 200, use 201 for resource creation */
|
|
467
996
|
successStatus?: number;
|
|
468
|
-
/**
|
|
997
|
+
/** CASL authorization configuration */
|
|
469
998
|
casl?: EntityCaslConfig;
|
|
470
|
-
/**
|
|
999
|
+
/** API route prefix for the action endpoint */
|
|
471
1000
|
routePrefix?: string;
|
|
472
|
-
/**
|
|
1001
|
+
/** UI sidebar ordering. Lower values appear first (default: 999) */
|
|
473
1002
|
order?: number;
|
|
474
1003
|
}
|
|
475
1004
|
/**
|
|
476
|
-
*
|
|
477
|
-
*
|
|
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
|
+
* ```
|
|
478
1026
|
*/
|
|
479
1027
|
interface ExternalEntityDefinition {
|
|
1028
|
+
/** Entity type identifier */
|
|
480
1029
|
type: 'external';
|
|
481
|
-
/**
|
|
482
|
-
label:
|
|
483
|
-
/**
|
|
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 */
|
|
484
1035
|
labelField: string;
|
|
485
|
-
/**
|
|
1036
|
+
/** Field definitions mapping to the external API response structure */
|
|
486
1037
|
fields: Record<string, FieldDefinition>;
|
|
487
|
-
/**
|
|
1038
|
+
/** Registered adapter name that handles API communication (e.g., 'stripe', 'github', 'salesforce') */
|
|
488
1039
|
adapter: string;
|
|
489
|
-
/**
|
|
1040
|
+
/** Configuration passed to the adapter */
|
|
490
1041
|
source?: {
|
|
491
|
-
/**
|
|
1042
|
+
/** API endpoint or resource path */
|
|
492
1043
|
endpoint?: string;
|
|
493
|
-
/**
|
|
1044
|
+
/** Additional adapter-specific configuration */
|
|
494
1045
|
[key: string]: unknown;
|
|
495
1046
|
};
|
|
496
|
-
/**
|
|
1047
|
+
/** Response caching to reduce API calls */
|
|
497
1048
|
cache?: {
|
|
498
|
-
/**
|
|
1049
|
+
/** Cache time-to-live in seconds */
|
|
499
1050
|
ttl: number;
|
|
500
|
-
/**
|
|
1051
|
+
/** Field used to generate unique cache keys */
|
|
501
1052
|
key?: string;
|
|
502
|
-
/**
|
|
1053
|
+
/** Event patterns that invalidate cached data (e.g., ['stripe.customer.*']) */
|
|
503
1054
|
invalidateOn?: string[];
|
|
504
1055
|
};
|
|
505
|
-
/**
|
|
1056
|
+
/** CASL authorization for read operations */
|
|
506
1057
|
casl?: EntityCaslConfig;
|
|
507
|
-
/**
|
|
1058
|
+
/** API route prefix */
|
|
508
1059
|
routePrefix?: string;
|
|
509
|
-
/**
|
|
1060
|
+
/** UI sidebar ordering (default: 999) */
|
|
510
1061
|
order?: number;
|
|
1062
|
+
/** UI display mode: 'table', 'list', or 'masonry' */
|
|
1063
|
+
displayMode?: DisplayMode;
|
|
511
1064
|
}
|
|
512
1065
|
/**
|
|
513
|
-
*
|
|
514
|
-
*
|
|
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
|
+
* ```
|
|
515
1086
|
*/
|
|
516
1087
|
interface VirtualEntityDefinition {
|
|
1088
|
+
/** Entity type identifier */
|
|
517
1089
|
type: 'virtual';
|
|
518
|
-
/**
|
|
519
|
-
label:
|
|
520
|
-
/**
|
|
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 */
|
|
521
1095
|
labelField: string;
|
|
522
|
-
/**
|
|
1096
|
+
/** Unified field definitions for the combined schema */
|
|
523
1097
|
fields: Record<string, FieldDefinition>;
|
|
524
|
-
/**
|
|
1098
|
+
/** Entity or table names to fetch data from */
|
|
525
1099
|
sources: string[];
|
|
526
|
-
/**
|
|
1100
|
+
/** Custom function to combine and transform data from all sources */
|
|
527
1101
|
resolver?: (sources: Record<string, unknown[]>, ctx: ModuleContext) => unknown[] | Promise<unknown[]>;
|
|
528
|
-
/**
|
|
1102
|
+
/** CASL authorization for read operations */
|
|
529
1103
|
casl?: EntityCaslConfig;
|
|
530
|
-
/**
|
|
1104
|
+
/** API route prefix */
|
|
531
1105
|
routePrefix?: string;
|
|
532
|
-
/**
|
|
1106
|
+
/** UI sidebar ordering (default: 999) */
|
|
533
1107
|
order?: number;
|
|
1108
|
+
/** UI display mode: 'table', 'list', or 'masonry' */
|
|
1109
|
+
displayMode?: DisplayMode;
|
|
534
1110
|
}
|
|
535
1111
|
/**
|
|
536
|
-
*
|
|
537
|
-
*
|
|
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
|
+
* ```
|
|
538
1136
|
*/
|
|
539
1137
|
interface ComputedEntityDefinition {
|
|
1138
|
+
/** Entity type identifier */
|
|
540
1139
|
type: 'computed';
|
|
541
|
-
/**
|
|
542
|
-
label:
|
|
543
|
-
/**
|
|
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) */
|
|
544
1145
|
labelField?: string;
|
|
545
|
-
/**
|
|
1146
|
+
/** Field definitions for the computed result structure */
|
|
546
1147
|
fields: Record<string, FieldDefinition>;
|
|
547
|
-
/**
|
|
1148
|
+
/** Function that computes and returns the data. Receives query params from URL */
|
|
548
1149
|
compute?: (ctx: ModuleContext, params?: Record<string, unknown>) => Promise<unknown[]>;
|
|
549
|
-
/**
|
|
1150
|
+
/** Caching to avoid recomputation on every request */
|
|
550
1151
|
cache?: {
|
|
551
|
-
/**
|
|
1152
|
+
/** Cache time-to-live in seconds. 0 = no caching */
|
|
552
1153
|
ttl: number;
|
|
553
|
-
/**
|
|
1154
|
+
/** Event patterns that trigger cache invalidation */
|
|
554
1155
|
invalidateOn?: string[];
|
|
555
1156
|
};
|
|
556
|
-
/**
|
|
1157
|
+
/** If true, returns single object instead of array (for dashboards, system info) */
|
|
1158
|
+
isSingle?: boolean;
|
|
1159
|
+
/** CASL authorization for read operations */
|
|
557
1160
|
casl?: EntityCaslConfig;
|
|
558
|
-
/**
|
|
1161
|
+
/** API route prefix */
|
|
559
1162
|
routePrefix?: string;
|
|
560
|
-
/**
|
|
1163
|
+
/** UI sidebar ordering (default: 999) */
|
|
561
1164
|
order?: number;
|
|
1165
|
+
/** UI display mode: 'table', 'list', or 'masonry' */
|
|
1166
|
+
displayMode?: DisplayMode;
|
|
562
1167
|
}
|
|
563
1168
|
/**
|
|
564
|
-
*
|
|
565
|
-
*
|
|
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
|
+
* ```
|
|
566
1188
|
*/
|
|
567
1189
|
interface ViewEntityDefinition {
|
|
1190
|
+
/** Entity type identifier */
|
|
568
1191
|
type: 'view';
|
|
569
|
-
/**
|
|
1192
|
+
/** Database table or SQL VIEW name */
|
|
570
1193
|
table: string;
|
|
571
|
-
/**
|
|
572
|
-
label:
|
|
573
|
-
/**
|
|
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 */
|
|
574
1199
|
labelField: string;
|
|
575
|
-
/**
|
|
1200
|
+
/** Field definitions for the view columns */
|
|
576
1201
|
fields: Record<string, FieldDefinition>;
|
|
577
|
-
/**
|
|
1202
|
+
/** Source entity this view derives from (for documentation) */
|
|
578
1203
|
sourceEntity?: string;
|
|
579
|
-
/**
|
|
1204
|
+
/** SQL query string or Knex query builder function to define the view */
|
|
580
1205
|
query?: string | ((db: Knex) => Knex.QueryBuilder);
|
|
581
|
-
/**
|
|
1206
|
+
/** CASL authorization for read operations */
|
|
582
1207
|
casl?: EntityCaslConfig;
|
|
583
|
-
/**
|
|
1208
|
+
/** API route prefix */
|
|
584
1209
|
routePrefix?: string;
|
|
585
|
-
/**
|
|
1210
|
+
/** UI sidebar ordering (default: 999) */
|
|
586
1211
|
order?: number;
|
|
587
1212
|
}
|
|
588
1213
|
/**
|
|
589
|
-
*
|
|
590
|
-
*
|
|
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
|
+
* ```
|
|
591
1236
|
*/
|
|
592
1237
|
interface ConfigEntityDefinition extends BaseEntityDefinition {
|
|
1238
|
+
/** Entity type identifier */
|
|
593
1239
|
type: 'config';
|
|
594
|
-
/**
|
|
1240
|
+
/** Field that identifies the configuration scope (e.g., 'module_name', 'tenant_id', 'user_id') */
|
|
595
1241
|
scopeField?: string;
|
|
596
|
-
/**
|
|
1242
|
+
/** Configuration scope level: 'global', 'module', 'tenant', or 'user' */
|
|
597
1243
|
scope?: 'global' | 'module' | 'tenant' | 'user';
|
|
598
|
-
/**
|
|
1244
|
+
/** Default values applied when config is first accessed */
|
|
599
1245
|
defaults?: Record<string, unknown>;
|
|
600
|
-
/**
|
|
1246
|
+
/** Auto-add updated_at timestamp column */
|
|
601
1247
|
timestamps?: boolean;
|
|
602
|
-
/**
|
|
1248
|
+
/** Auto-add updated_by user reference column */
|
|
603
1249
|
audit?: boolean;
|
|
604
1250
|
}
|
|
605
1251
|
/**
|
|
606
|
-
*
|
|
607
|
-
*
|
|
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
|
+
* ```
|
|
608
1272
|
*/
|
|
609
1273
|
interface TempEntityDefinition extends BaseEntityDefinition {
|
|
1274
|
+
/** Entity type identifier */
|
|
610
1275
|
type: 'temp';
|
|
611
|
-
/**
|
|
1276
|
+
/** Time-to-live in seconds. Records are deleted after this duration */
|
|
612
1277
|
ttl: number;
|
|
613
|
-
/**
|
|
1278
|
+
/** Field that stores the expiration timestamp (default: 'expires_at') */
|
|
614
1279
|
ttlField?: string;
|
|
615
|
-
/**
|
|
1280
|
+
/** Field used as display label in lists (optional for temp entities) */
|
|
616
1281
|
labelField?: string;
|
|
617
|
-
/**
|
|
1282
|
+
/** Database indexes for efficient lookups */
|
|
618
1283
|
indexes?: EntityIndex[];
|
|
619
1284
|
}
|
|
620
1285
|
/**
|
|
621
|
-
*
|
|
1286
|
+
* All available entity type identifiers.
|
|
1287
|
+
*
|
|
1288
|
+
* @see {@link EntityDefinition} for the full discriminated union with documentation
|
|
622
1289
|
*/
|
|
623
1290
|
type EntityType = 'collection' | 'single' | 'external' | 'virtual' | 'computed' | 'view' | 'reference' | 'config' | 'event' | 'temp' | 'action';
|
|
624
1291
|
/**
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
* |
|
|
636
|
-
*
|
|
637
|
-
* |
|
|
638
|
-
* |
|
|
639
|
-
* |
|
|
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 |
|
|
640
1315
|
*/
|
|
641
1316
|
type EntityDefinition = CollectionEntityDefinition | SingleEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition | ComputedEntityDefinition | ViewEntityDefinition | ReferenceEntityDefinition | ConfigEntityDefinition | EventEntityDefinition | TempEntityDefinition | ActionEntityDefinition;
|
|
642
1317
|
/**
|
|
643
|
-
*
|
|
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
|
+
* ```
|
|
644
1329
|
*/
|
|
645
1330
|
interface ModuleRequirements {
|
|
1331
|
+
/** Environment variables that must be defined */
|
|
646
1332
|
env?: string[];
|
|
1333
|
+
/** Other modules that must be loaded first */
|
|
647
1334
|
modules?: string[];
|
|
648
1335
|
}
|
|
649
1336
|
/**
|
|
650
|
-
*
|
|
651
|
-
*
|
|
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
|
+
* ```
|
|
652
1351
|
*/
|
|
653
1352
|
interface AbilityLike {
|
|
1353
|
+
/** Check if action is allowed on subject */
|
|
654
1354
|
can: (action: string, subject: unknown) => boolean;
|
|
1355
|
+
/** Check if action is denied on subject */
|
|
655
1356
|
cannot: (action: string, subject: unknown) => boolean;
|
|
656
1357
|
}
|
|
657
1358
|
/**
|
|
658
|
-
*
|
|
1359
|
+
* CASL ForbiddenError instance for throwing permission errors.
|
|
659
1360
|
*/
|
|
660
1361
|
interface ForbiddenErrorInstance {
|
|
1362
|
+
/** Throws ForbiddenError if action is not allowed on subject */
|
|
661
1363
|
throwUnlessCan: (action: string, subject: unknown) => void;
|
|
662
1364
|
}
|
|
663
1365
|
/**
|
|
664
|
-
*
|
|
1366
|
+
* CASL ForbiddenError constructor for creating error instances.
|
|
665
1367
|
*/
|
|
666
1368
|
interface ForbiddenErrorConstructor {
|
|
1369
|
+
/** Creates a ForbiddenErrorInstance bound to an ability */
|
|
667
1370
|
from: (ability: any) => ForbiddenErrorInstance;
|
|
668
1371
|
}
|
|
669
1372
|
/**
|
|
670
|
-
*
|
|
671
|
-
*
|
|
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
|
+
* ```
|
|
672
1383
|
*/
|
|
673
1384
|
interface ModuleAbilities {
|
|
674
|
-
/**
|
|
675
|
-
defineAbilityFor: (user:
|
|
676
|
-
|
|
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) */
|
|
677
1390
|
packRules: (ability: unknown) => unknown[];
|
|
678
|
-
/**
|
|
1391
|
+
/** Wraps an object as CASL subject for instance-level permission checks */
|
|
679
1392
|
subject: (type: string, object: Record<string, unknown>) => unknown;
|
|
680
|
-
/**
|
|
1393
|
+
/** ForbiddenError constructor for throwUnlessCan pattern */
|
|
681
1394
|
ForbiddenError: ForbiddenErrorConstructor;
|
|
682
1395
|
}
|
|
683
1396
|
/**
|
|
684
|
-
*
|
|
685
|
-
*
|
|
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
|
+
* ```
|
|
686
1408
|
*/
|
|
687
|
-
type
|
|
1409
|
+
type AuthRequest = BaseAuthRequest<BaseUser, AbilityLike>;
|
|
688
1410
|
/**
|
|
689
|
-
*
|
|
1411
|
+
* Helper utilities available in module context.
|
|
1412
|
+
*
|
|
1413
|
+
* Provides common operations for migrations, ID generation, and path resolution.
|
|
690
1414
|
*/
|
|
691
1415
|
interface ContextHelpers {
|
|
1416
|
+
/** Generates a unique UUID v4 identifier */
|
|
692
1417
|
generateId: () => string;
|
|
1418
|
+
/** Adds created_at and updated_at columns to a table during migration */
|
|
693
1419
|
addTimestamps: (table: Knex.CreateTableBuilder, db: Knex) => void;
|
|
1420
|
+
/** Adds created_by and updated_by columns if they don't exist */
|
|
694
1421
|
addAuditFieldsIfMissing: (db: Knex, tableName: string) => Promise<void>;
|
|
695
|
-
/**
|
|
1422
|
+
/** Adds is_default boolean field for config tables */
|
|
696
1423
|
addConfigDefaultField: (db: Knex, tableName: string) => Promise<void>;
|
|
1424
|
+
/** Adds a column only if it doesn't exist. Returns true if column was added */
|
|
697
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 */
|
|
698
1427
|
getLibPath: () => string;
|
|
1428
|
+
/** Gets the path to the user's project root directory */
|
|
699
1429
|
getProjectPath: () => string;
|
|
700
|
-
/**
|
|
1430
|
+
/** Returns current timestamp formatted for the DB driver (MySQL: 'YYYY-MM-DD HH:MM:SS', others: ISO 8601) */
|
|
701
1431
|
nowTimestamp: (db: Knex) => string;
|
|
702
|
-
/**
|
|
1432
|
+
/** Formats a Date object for the current DB driver */
|
|
703
1433
|
formatTimestamp: (db: Knex, date?: Date) => string;
|
|
704
1434
|
}
|
|
705
1435
|
/**
|
|
706
|
-
*
|
|
1436
|
+
* Cryptographic utilities for password handling.
|
|
1437
|
+
*
|
|
1438
|
+
* Uses bcrypt with cost factor 12 for secure password hashing.
|
|
707
1439
|
*/
|
|
708
1440
|
interface ContextCrypto {
|
|
709
|
-
/**
|
|
1441
|
+
/** Hashes a password using bcrypt (cost factor 12) */
|
|
710
1442
|
hashPassword: (password: string) => Promise<string>;
|
|
711
|
-
/**
|
|
1443
|
+
/** Verifies password against bcrypt hash (timing-safe comparison) */
|
|
712
1444
|
verifyPassword: (password: string, hash: string) => Promise<boolean>;
|
|
713
|
-
/**
|
|
1445
|
+
/** Pre-computed dummy hash for timing-safe comparison when user doesn't exist */
|
|
714
1446
|
DUMMY_HASH: string;
|
|
715
1447
|
}
|
|
716
1448
|
/**
|
|
717
|
-
*
|
|
718
|
-
*
|
|
1449
|
+
* Socket.IO real-time communication API.
|
|
1450
|
+
*
|
|
1451
|
+
* Provides access to WebSocket connections for real-time features.
|
|
1452
|
+
* Only available if Socket.IO is initialized in the backend configuration.
|
|
719
1453
|
*/
|
|
720
1454
|
interface ContextSocket {
|
|
721
|
-
/**
|
|
1455
|
+
/** Gets the Socket.IO server instance. Throws if not initialized */
|
|
722
1456
|
getIO: () => unknown;
|
|
723
|
-
/**
|
|
1457
|
+
/** Checks if Socket.IO server is running */
|
|
724
1458
|
isInitialized: () => boolean;
|
|
725
|
-
/**
|
|
1459
|
+
/** Checks if a specific user has active WebSocket connections */
|
|
726
1460
|
isUserConnected: (userId: string) => boolean;
|
|
727
|
-
/**
|
|
1461
|
+
/** Gets the number of active sockets for a user (multiple tabs/devices) */
|
|
728
1462
|
getUserSocketCount: (userId: string) => number;
|
|
729
|
-
/**
|
|
1463
|
+
/** Gets array of all connected user IDs */
|
|
730
1464
|
getConnectedUsers: () => string[];
|
|
731
1465
|
}
|
|
732
1466
|
/**
|
|
733
|
-
*
|
|
734
|
-
*
|
|
1467
|
+
* Application manifest with module and plugin registry.
|
|
1468
|
+
*
|
|
1469
|
+
* Represents either the core nexus-backend manifest or a user project manifest.
|
|
1470
|
+
*/
|
|
1471
|
+
interface AppManifest {
|
|
1472
|
+
/** Package name from package.json */
|
|
1473
|
+
name: string;
|
|
1474
|
+
/** Package version from package.json */
|
|
1475
|
+
version: string;
|
|
1476
|
+
/** Registered modules in this application */
|
|
1477
|
+
modules: ModuleManifest[];
|
|
1478
|
+
/** Registered plugins (only present in user manifest) */
|
|
1479
|
+
plugins?: PluginManifest[];
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Engine API for runtime introspection of modules and plugins.
|
|
1483
|
+
*
|
|
1484
|
+
* Provides access to the complete registry of loaded components.
|
|
735
1485
|
*/
|
|
736
1486
|
interface ContextEngine {
|
|
737
|
-
/**
|
|
1487
|
+
/** Gets all registered modules (core + user) */
|
|
738
1488
|
getModules: () => ModuleManifest[];
|
|
739
|
-
/**
|
|
1489
|
+
/** Gets all registered plugins */
|
|
740
1490
|
getPlugins: () => PluginManifest[];
|
|
741
|
-
/**
|
|
1491
|
+
/** Gets CASL subjects defined by a specific module */
|
|
742
1492
|
getModuleSubjects: (mod: ModuleManifest) => string[];
|
|
743
|
-
/**
|
|
1493
|
+
/** Gets all CASL subjects registered across all modules */
|
|
744
1494
|
getRegisteredSubjects: () => string[];
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1495
|
+
/** Gets modules from nexus-backend core (auth, users, storage, etc.) */
|
|
1496
|
+
getCoreModules: () => ModuleManifest[];
|
|
1497
|
+
/** Gets modules from user project and plugins */
|
|
1498
|
+
getUserModules: () => ModuleManifest[];
|
|
1499
|
+
/** Gets the nexus-backend library manifest */
|
|
1500
|
+
getCoreManifest: () => AppManifest;
|
|
1501
|
+
/** Gets the user project manifest. Returns null in standalone development mode */
|
|
1502
|
+
getUserManifest: () => AppManifest | null;
|
|
1503
|
+
/** Returns true if running as a library inside a user project */
|
|
1504
|
+
hasUserApp: () => boolean;
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Generic validation schema interface compatible with Zod.
|
|
1508
|
+
*
|
|
1509
|
+
* Any object with a parse() method that throws on invalid data works.
|
|
749
1510
|
*/
|
|
750
1511
|
interface ValidationSchema {
|
|
1512
|
+
/** Parses and validates data. Throws on validation failure */
|
|
751
1513
|
parse: (data: unknown) => unknown;
|
|
752
1514
|
}
|
|
753
1515
|
/**
|
|
754
|
-
*
|
|
1516
|
+
* Schemas for the validation middleware.
|
|
1517
|
+
*
|
|
1518
|
+
* Validates request body, query parameters, and URL parameters.
|
|
755
1519
|
*/
|
|
756
1520
|
interface ValidateSchemas {
|
|
1521
|
+
/** Schema for request body (req.body) */
|
|
757
1522
|
body?: ValidationSchema;
|
|
1523
|
+
/** Schema for query parameters (req.query) */
|
|
758
1524
|
query?: ValidationSchema;
|
|
1525
|
+
/** Schema for URL parameters (req.params) */
|
|
759
1526
|
params?: ValidationSchema;
|
|
760
1527
|
}
|
|
761
1528
|
/**
|
|
762
|
-
*
|
|
1529
|
+
* Configuration options for rate limiting middleware.
|
|
1530
|
+
*/
|
|
1531
|
+
interface RateLimitOptions {
|
|
1532
|
+
/** Time window duration in milliseconds (default: 60000 = 1 minute) */
|
|
1533
|
+
windowMs?: number;
|
|
1534
|
+
/** Maximum requests allowed per window (default: 100) */
|
|
1535
|
+
max?: number;
|
|
1536
|
+
/** Error message when rate limit is exceeded */
|
|
1537
|
+
message?: string;
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Middleware factories available in module context.
|
|
1541
|
+
*
|
|
1542
|
+
* Provides common Express middlewares for validation, rate limiting, etc.
|
|
763
1543
|
*/
|
|
764
1544
|
interface ModuleMiddlewares {
|
|
1545
|
+
/** Creates validation middleware for body/query/params */
|
|
765
1546
|
validate: (schemas: ValidateSchemas) => RequestHandler;
|
|
1547
|
+
/** Creates rate limiting middleware */
|
|
1548
|
+
rateLimit: (options?: RateLimitOptions) => RequestHandler;
|
|
1549
|
+
/** Extensible: additional middlewares can be registered */
|
|
766
1550
|
[key: string]: RequestHandler | ((...args: any[]) => RequestHandler) | undefined;
|
|
767
1551
|
}
|
|
768
1552
|
/**
|
|
769
|
-
*
|
|
1553
|
+
* Minimal EventEmitter interface compatible with EventEmitter2.
|
|
1554
|
+
*
|
|
1555
|
+
* Used for inter-module communication and event-driven architecture.
|
|
770
1556
|
*/
|
|
771
1557
|
interface EventEmitterLike {
|
|
1558
|
+
/** Emits an event with arguments. Returns true if listeners exist */
|
|
772
1559
|
emit: (event: string, ...args: unknown[]) => boolean;
|
|
1560
|
+
/** Registers an event listener */
|
|
773
1561
|
on: (event: string, listener: (...args: unknown[]) => void) => this;
|
|
1562
|
+
/** Removes an event listener */
|
|
774
1563
|
off: (event: string, listener: (...args: unknown[]) => void) => this;
|
|
1564
|
+
/** Registers a one-time event listener */
|
|
775
1565
|
once: (event: string, listener: (...args: unknown[]) => void) => this;
|
|
1566
|
+
/** Registers a listener for all events (EventEmitter2 feature) */
|
|
776
1567
|
onAny?: (listener: (event: string, ...args: unknown[]) => void) => this;
|
|
777
1568
|
}
|
|
778
1569
|
/**
|
|
779
|
-
*
|
|
1570
|
+
* Error reporter interface for external monitoring services.
|
|
1571
|
+
*
|
|
1572
|
+
* Compatible with Sentry, GlitchTip, or any error tracking service.
|
|
780
1573
|
*/
|
|
781
1574
|
interface LoggerReporter {
|
|
1575
|
+
/** Captures and reports an exception to the monitoring service */
|
|
782
1576
|
captureException: (error: Error, context?: Record<string, unknown>) => void;
|
|
783
1577
|
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Filter operators for advanced filtering
|
|
1580
|
+
*
|
|
1581
|
+
* @example
|
|
1582
|
+
* // Simple equality (shorthand)
|
|
1583
|
+
* { status: 'active' }
|
|
1584
|
+
*
|
|
1585
|
+
* // With operators
|
|
1586
|
+
* { name: { $contains: 'john' } }
|
|
1587
|
+
* { age: { $gte: 18 } }
|
|
1588
|
+
* { email: { $startswith: 'admin' } }
|
|
1589
|
+
*
|
|
1590
|
+
* // Array (IN operator shorthand)
|
|
1591
|
+
* { status: ['active', 'pending'] }
|
|
1592
|
+
*/
|
|
1593
|
+
interface FilterOperators {
|
|
1594
|
+
/** Equal (default if no operator) */
|
|
1595
|
+
$eq?: unknown;
|
|
1596
|
+
/** Not equal */
|
|
1597
|
+
$ne?: unknown;
|
|
1598
|
+
/** Greater than */
|
|
1599
|
+
$gt?: number | string | Date;
|
|
1600
|
+
/** Greater than or equal */
|
|
1601
|
+
$gte?: number | string | Date;
|
|
1602
|
+
/** Less than */
|
|
1603
|
+
$lt?: number | string | Date;
|
|
1604
|
+
/** Less than or equal */
|
|
1605
|
+
$lte?: number | string | Date;
|
|
1606
|
+
/** Contains (LIKE %value%) */
|
|
1607
|
+
$contains?: string;
|
|
1608
|
+
/** Starts with (LIKE value%) */
|
|
1609
|
+
$startswith?: string;
|
|
1610
|
+
/** Ends with (LIKE %value) */
|
|
1611
|
+
$endswith?: string;
|
|
1612
|
+
/** In array */
|
|
1613
|
+
$in?: unknown[];
|
|
1614
|
+
/** Not in array */
|
|
1615
|
+
$nin?: unknown[];
|
|
1616
|
+
/** Is null (true = IS NULL, false = IS NOT NULL) */
|
|
1617
|
+
$isnull?: boolean;
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Filter value can be:
|
|
1621
|
+
* - Primitive (shorthand for $eq)
|
|
1622
|
+
* - Array (shorthand for $in)
|
|
1623
|
+
* - Object with operators
|
|
1624
|
+
*/
|
|
1625
|
+
type FilterValue = string | number | boolean | null | unknown[] | FilterOperators;
|
|
784
1626
|
/**
|
|
785
1627
|
* Query parameters for entity list operations
|
|
1628
|
+
*
|
|
1629
|
+
* @example
|
|
1630
|
+
* // Simple filters
|
|
1631
|
+
* { filters: { status: 'active' } }
|
|
1632
|
+
*
|
|
1633
|
+
* // With operators (see FilterOperators)
|
|
1634
|
+
* { filters: { name: { $contains: 'john' }, age: { $gte: 18 } } }
|
|
786
1635
|
*/
|
|
787
1636
|
interface EntityQuery {
|
|
788
1637
|
page?: number;
|
|
@@ -790,52 +1639,92 @@ interface EntityQuery {
|
|
|
790
1639
|
sort?: string;
|
|
791
1640
|
order?: 'asc' | 'desc';
|
|
792
1641
|
search?: string;
|
|
1642
|
+
/** Filters with optional operators - use FilterValue type for type safety */
|
|
793
1643
|
filters?: Record<string, unknown>;
|
|
794
1644
|
}
|
|
795
1645
|
/**
|
|
796
|
-
*
|
|
797
|
-
*
|
|
1646
|
+
* Role management service interface.
|
|
1647
|
+
*
|
|
1648
|
+
* Provides CRUD operations for roles. Used internally by UsersService.
|
|
798
1649
|
*/
|
|
799
1650
|
interface BaseRolesService {
|
|
1651
|
+
/** Gets paginated list of all roles */
|
|
800
1652
|
findAll(query?: EntityQuery): Promise<PaginatedResult<unknown>>;
|
|
1653
|
+
/** Gets a role by its ID */
|
|
801
1654
|
findById(id: string): Promise<unknown>;
|
|
1655
|
+
/** Gets a role by its name (e.g., 'ADMIN', 'EDITOR') */
|
|
802
1656
|
findByName(name: string): Promise<unknown | undefined>;
|
|
803
|
-
|
|
1657
|
+
/** Creates a new role */
|
|
804
1658
|
create(data: Record<string, unknown>, userId?: string): Promise<unknown>;
|
|
1659
|
+
/** Updates an existing role */
|
|
805
1660
|
update(id: string, data: Record<string, unknown>, userId?: string): Promise<unknown>;
|
|
1661
|
+
/** Deletes a role by ID */
|
|
806
1662
|
delete(id: string): Promise<void>;
|
|
807
|
-
updatePermissions(id: string, permissions: unknown[], userId?: string): Promise<unknown>;
|
|
808
1663
|
}
|
|
809
1664
|
/**
|
|
810
|
-
*
|
|
811
|
-
*
|
|
1665
|
+
* User management service interface.
|
|
1666
|
+
*
|
|
1667
|
+
* Provides user CRUD and role assignment operations.
|
|
1668
|
+
* The backend extends this with concrete types (UserWithRole, etc.).
|
|
812
1669
|
*/
|
|
813
1670
|
interface BaseUsersService {
|
|
1671
|
+
/** Gets paginated list of users */
|
|
814
1672
|
findAll(query?: EntityQuery): Promise<PaginatedResult<unknown>>;
|
|
1673
|
+
/** Gets a user by ID without role information */
|
|
815
1674
|
findById(id: string): Promise<unknown>;
|
|
816
|
-
|
|
1675
|
+
/** Gets a user by ID including their assigned roles */
|
|
1676
|
+
findByIdWithRoles(id: string): Promise<unknown | null>;
|
|
1677
|
+
/** Creates a new user */
|
|
817
1678
|
create(data: Record<string, unknown>, userId?: string): Promise<unknown>;
|
|
1679
|
+
/** Updates an existing user */
|
|
818
1680
|
update(id: string, data: Record<string, unknown>, userId?: string): Promise<unknown>;
|
|
1681
|
+
/** Deletes a user by ID */
|
|
819
1682
|
delete(id: string): Promise<void>;
|
|
1683
|
+
/** Gets array of role IDs for a user */
|
|
1684
|
+
getRoleIds(userId: string): Promise<string[]>;
|
|
1685
|
+
/** Gets array of role names for a user (e.g., ['ADMIN', 'EDITOR']) */
|
|
1686
|
+
getRoleNames(userId: string): Promise<string[]>;
|
|
1687
|
+
/** Gets full role objects for a user */
|
|
1688
|
+
getUserRoles(userId: string): Promise<unknown[]>;
|
|
1689
|
+
/** Assigns a single role to a user (adds to existing roles) */
|
|
1690
|
+
assignRole(userId: string, roleId: string): Promise<void>;
|
|
1691
|
+
/** Removes a single role from a user */
|
|
1692
|
+
removeRole(userId: string, roleId: string): Promise<void>;
|
|
1693
|
+
/** Replaces all user roles with the provided list */
|
|
1694
|
+
setRoles(userId: string, roleIds: string[]): Promise<void>;
|
|
1695
|
+
/** Nested roles service for direct role management */
|
|
820
1696
|
roles: BaseRolesService;
|
|
821
1697
|
}
|
|
822
1698
|
/**
|
|
823
|
-
*
|
|
1699
|
+
* Email sending options.
|
|
1700
|
+
*
|
|
1701
|
+
* Supports both simple text emails and rich HTML templates with actions.
|
|
824
1702
|
*/
|
|
825
1703
|
interface SendMailOptions {
|
|
1704
|
+
/** Recipient email address(es) */
|
|
826
1705
|
to: string | string[];
|
|
1706
|
+
/** Email subject line */
|
|
827
1707
|
subject: string;
|
|
1708
|
+
/** Title for email template (displayed prominently) */
|
|
828
1709
|
title?: string;
|
|
1710
|
+
/** Plain text message for simple emails */
|
|
829
1711
|
message?: string;
|
|
1712
|
+
/** Full HTML content (overrides template) */
|
|
830
1713
|
html?: string;
|
|
1714
|
+
/** Plain text fallback (generated from HTML if not provided) */
|
|
831
1715
|
text?: string;
|
|
1716
|
+
/** Sender email address (uses default if not provided) */
|
|
832
1717
|
from?: string;
|
|
1718
|
+
/** Reply-to email address */
|
|
833
1719
|
replyTo?: string;
|
|
1720
|
+
/** CTA buttons for email template */
|
|
834
1721
|
actions?: Array<{
|
|
835
1722
|
label: string;
|
|
836
1723
|
url: string;
|
|
837
1724
|
}>;
|
|
1725
|
+
/** Logo URL for email header */
|
|
838
1726
|
logoUrl?: string;
|
|
1727
|
+
/** File attachments */
|
|
839
1728
|
attachments?: Array<{
|
|
840
1729
|
filename: string;
|
|
841
1730
|
content: Buffer | string;
|
|
@@ -843,290 +1732,642 @@ interface SendMailOptions {
|
|
|
843
1732
|
}>;
|
|
844
1733
|
}
|
|
845
1734
|
/**
|
|
846
|
-
*
|
|
1735
|
+
* Result from sending an email.
|
|
847
1736
|
*/
|
|
848
1737
|
interface SendMailResult {
|
|
1738
|
+
/** Unique message ID from the mail server */
|
|
849
1739
|
messageId: string;
|
|
1740
|
+
/** Email addresses that accepted the message */
|
|
850
1741
|
accepted: string[];
|
|
1742
|
+
/** Email addresses that rejected the message */
|
|
851
1743
|
rejected: string[];
|
|
852
1744
|
}
|
|
853
1745
|
/**
|
|
854
|
-
*
|
|
1746
|
+
* Email service interface for sending emails.
|
|
855
1747
|
*/
|
|
856
1748
|
interface BaseMailService {
|
|
1749
|
+
/** Sends an email. Returns null if mail service is not configured */
|
|
857
1750
|
send(options: SendMailOptions): Promise<SendMailResult | null>;
|
|
1751
|
+
/** Verifies SMTP connection is working */
|
|
858
1752
|
verify(): Promise<boolean>;
|
|
859
1753
|
}
|
|
860
1754
|
/**
|
|
861
|
-
*
|
|
862
|
-
*
|
|
1755
|
+
* Core services available via ctx.services.
|
|
1756
|
+
*
|
|
1757
|
+
* Modules can extend this with additional services.
|
|
863
1758
|
*/
|
|
864
1759
|
interface CoreServices {
|
|
1760
|
+
/** Error logging and reporting service */
|
|
865
1761
|
logger?: LoggerReporter;
|
|
1762
|
+
/** User management service */
|
|
866
1763
|
users?: BaseUsersService;
|
|
1764
|
+
/** Email sending service */
|
|
867
1765
|
mail?: BaseMailService;
|
|
1766
|
+
/** Extensible: additional services registered by modules */
|
|
868
1767
|
[key: string]: unknown;
|
|
869
1768
|
}
|
|
870
1769
|
/**
|
|
871
|
-
*
|
|
1770
|
+
* Base interface for all entity services.
|
|
1771
|
+
*
|
|
1772
|
+
* Provides read operations common to all entity types.
|
|
872
1773
|
*/
|
|
873
1774
|
interface BaseEntityService<T = unknown> {
|
|
1775
|
+
/** Gets paginated list of records with optional filtering/sorting */
|
|
874
1776
|
findAll(query?: EntityQuery): Promise<PaginatedResult<T>>;
|
|
1777
|
+
/** Gets a single record by ID. Returns null if not found */
|
|
875
1778
|
findById(id: string): Promise<T | null>;
|
|
1779
|
+
/** The entity definition this service operates on */
|
|
876
1780
|
readonly definition: EntityDefinition;
|
|
877
1781
|
}
|
|
878
1782
|
/**
|
|
879
|
-
*
|
|
1783
|
+
* Service for Collection entities with full CRUD operations.
|
|
1784
|
+
*
|
|
1785
|
+
* @see {@link CollectionEntityDefinition}
|
|
880
1786
|
*/
|
|
881
1787
|
interface CollectionEntityService<T = unknown> extends BaseEntityService<T> {
|
|
1788
|
+
/** Creates a new record. userId is used for audit fields */
|
|
882
1789
|
create(data: Partial<T>, userId?: string): Promise<T>;
|
|
1790
|
+
/** Updates an existing record by ID */
|
|
883
1791
|
update(id: string, data: Partial<T>, userId?: string): Promise<T>;
|
|
1792
|
+
/** Deletes a record by ID (soft delete if configured) */
|
|
884
1793
|
delete(id: string): Promise<void>;
|
|
885
1794
|
}
|
|
886
1795
|
/**
|
|
887
|
-
*
|
|
1796
|
+
* Service for Temp entities with automatic TTL expiration.
|
|
1797
|
+
*
|
|
1798
|
+
* @see {@link TempEntityDefinition}
|
|
888
1799
|
*/
|
|
889
1800
|
interface TempEntityService<T = unknown> extends BaseEntityService<T> {
|
|
1801
|
+
/** Creates a temporary record with automatic expiration */
|
|
890
1802
|
create(data: Partial<T>): Promise<T>;
|
|
1803
|
+
/** Updates a record. Optionally extends TTL from current time */
|
|
891
1804
|
update(id: string, data: Partial<T>, extendTtl?: boolean): Promise<T>;
|
|
1805
|
+
/** Deletes a record immediately */
|
|
892
1806
|
delete(id: string): Promise<void>;
|
|
1807
|
+
/** Extends the TTL of a record by additional seconds */
|
|
893
1808
|
extendTtl(id: string, additionalSeconds?: number): Promise<T>;
|
|
1809
|
+
/** Removes all expired records. Returns count of deleted records */
|
|
894
1810
|
cleanup(): Promise<number>;
|
|
1811
|
+
/** Gets remaining TTL in seconds. Returns null if expired or not found */
|
|
895
1812
|
getRemainingTtl(id: string): Promise<number | null>;
|
|
1813
|
+
/** Checks if a record exists and hasn't expired */
|
|
896
1814
|
isValid(id: string): Promise<boolean>;
|
|
897
1815
|
}
|
|
898
1816
|
/**
|
|
899
|
-
*
|
|
1817
|
+
* Service for Event entities (append-only audit logs).
|
|
1818
|
+
*
|
|
1819
|
+
* @see {@link EventEntityDefinition}
|
|
900
1820
|
*/
|
|
901
1821
|
interface EventEntityService<T = unknown> extends BaseEntityService<T> {
|
|
1822
|
+
/** Creates a new event record (append-only) */
|
|
902
1823
|
create(data: Partial<T>): Promise<T>;
|
|
1824
|
+
/** Alias for create - appends a new event */
|
|
903
1825
|
append(data: Partial<T>): Promise<T>;
|
|
1826
|
+
/** Runs retention policy cleanup. Returns count of deleted records */
|
|
904
1827
|
runRetentionCleanup(): Promise<number>;
|
|
1828
|
+
/** Queries events within a date range */
|
|
905
1829
|
findByDateRange(start: Date, end: Date, query?: EntityQuery): Promise<PaginatedResult<T>>;
|
|
906
1830
|
}
|
|
907
1831
|
/**
|
|
908
|
-
*
|
|
1832
|
+
* Service for Single entities (global configuration).
|
|
1833
|
+
*
|
|
1834
|
+
* @see {@link SingleEntityDefinition}
|
|
909
1835
|
*/
|
|
910
1836
|
interface SingleEntityService<T = unknown> extends BaseEntityService<T> {
|
|
1837
|
+
/** Gets the single record (creates with defaults if not exists) */
|
|
911
1838
|
get(): Promise<T | null>;
|
|
1839
|
+
/** Updates the single record */
|
|
912
1840
|
update(data: Partial<T>, userId?: string): Promise<T>;
|
|
913
1841
|
}
|
|
914
1842
|
/**
|
|
915
|
-
*
|
|
1843
|
+
* Service for Config entities (scoped key-value configuration).
|
|
1844
|
+
*
|
|
1845
|
+
* @see {@link ConfigEntityDefinition}
|
|
916
1846
|
*/
|
|
917
1847
|
interface ConfigEntityService<T = unknown> extends BaseEntityService<T> {
|
|
1848
|
+
/** Gets configuration value by key */
|
|
918
1849
|
get(key: string): Promise<T | null>;
|
|
1850
|
+
/** Sets configuration value for a key */
|
|
919
1851
|
set(key: string, value: unknown, userId?: string): Promise<T>;
|
|
1852
|
+
/** Gets all configuration as key-value object */
|
|
920
1853
|
getAll(): Promise<Record<string, unknown>>;
|
|
921
1854
|
}
|
|
922
|
-
/**
|
|
1855
|
+
/**
|
|
1856
|
+
* Service for Reference entities (read-only catalogs).
|
|
1857
|
+
* @see {@link ReferenceEntityDefinition}
|
|
1858
|
+
*/
|
|
923
1859
|
type ReferenceEntityService<T = unknown> = BaseEntityService<T>;
|
|
924
|
-
/**
|
|
1860
|
+
/**
|
|
1861
|
+
* Service for View entities (SQL views, read-only).
|
|
1862
|
+
* @see {@link ViewEntityDefinition}
|
|
1863
|
+
*/
|
|
925
1864
|
type ViewEntityService<T = unknown> = BaseEntityService<T>;
|
|
926
|
-
/**
|
|
1865
|
+
/**
|
|
1866
|
+
* Service for Computed entities (dynamically calculated data).
|
|
1867
|
+
* @see {@link ComputedEntityDefinition}
|
|
1868
|
+
*/
|
|
927
1869
|
type ComputedEntityService<T = unknown> = BaseEntityService<T>;
|
|
928
|
-
/**
|
|
1870
|
+
/**
|
|
1871
|
+
* Service for External entities (external API data).
|
|
1872
|
+
* @see {@link ExternalEntityDefinition}
|
|
1873
|
+
*/
|
|
929
1874
|
type ExternalEntityService<T = unknown> = BaseEntityService<T>;
|
|
930
|
-
/**
|
|
1875
|
+
/**
|
|
1876
|
+
* Service for Virtual entities (aggregated from multiple sources).
|
|
1877
|
+
* @see {@link VirtualEntityDefinition}
|
|
1878
|
+
*/
|
|
931
1879
|
type VirtualEntityService<T = unknown> = BaseEntityService<T>;
|
|
932
1880
|
/**
|
|
933
|
-
*
|
|
1881
|
+
* Service for Action entities (command execution).
|
|
1882
|
+
*
|
|
1883
|
+
* @see {@link ActionEntityDefinition}
|
|
934
1884
|
*/
|
|
935
1885
|
interface ActionEntityService<T = unknown> {
|
|
1886
|
+
/** Executes the action with provided input data */
|
|
936
1887
|
execute(data: Partial<T>): Promise<unknown>;
|
|
1888
|
+
/** The action definition this service implements */
|
|
937
1889
|
readonly definition: EntityDefinition;
|
|
938
1890
|
}
|
|
939
1891
|
/**
|
|
940
|
-
* Hooks
|
|
941
|
-
*
|
|
1892
|
+
* Hooks to customize entity service behavior.
|
|
1893
|
+
* Allows plugins to add logic without inheritance.
|
|
942
1894
|
*/
|
|
943
1895
|
interface EntityServiceHooks<T = unknown> {
|
|
944
1896
|
/**
|
|
945
|
-
*
|
|
946
|
-
* @param data
|
|
947
|
-
* @returns
|
|
1897
|
+
* Runs before creating a record.
|
|
1898
|
+
* @param data Data to insert
|
|
1899
|
+
* @returns Modified data
|
|
948
1900
|
*/
|
|
949
1901
|
beforeCreate?: (data: Partial<T>) => Promise<Partial<T>> | Partial<T>;
|
|
950
1902
|
/**
|
|
951
|
-
*
|
|
952
|
-
* @param record
|
|
1903
|
+
* Runs after creating a record.
|
|
1904
|
+
* @param record Created record
|
|
953
1905
|
*/
|
|
954
1906
|
afterCreate?: (record: T) => Promise<void> | void;
|
|
955
1907
|
/**
|
|
956
|
-
*
|
|
957
|
-
* @param id ID
|
|
958
|
-
* @param data
|
|
959
|
-
* @returns
|
|
1908
|
+
* Runs before updating a record.
|
|
1909
|
+
* @param id Record ID
|
|
1910
|
+
* @param data Data to update
|
|
1911
|
+
* @returns Modified data
|
|
960
1912
|
*/
|
|
961
1913
|
beforeUpdate?: (id: string, data: Partial<T>) => Promise<Partial<T>> | Partial<T>;
|
|
962
1914
|
/**
|
|
963
|
-
*
|
|
964
|
-
* @param record
|
|
1915
|
+
* Runs after updating a record.
|
|
1916
|
+
* @param record Updated record
|
|
965
1917
|
*/
|
|
966
1918
|
afterUpdate?: (record: T) => Promise<void> | void;
|
|
967
1919
|
/**
|
|
968
|
-
*
|
|
969
|
-
* @param id ID
|
|
1920
|
+
* Runs before deleting a record.
|
|
1921
|
+
* @param id Record ID
|
|
970
1922
|
*/
|
|
971
1923
|
beforeDelete?: (id: string) => Promise<void> | void;
|
|
972
1924
|
/**
|
|
973
|
-
*
|
|
974
|
-
* @param id ID
|
|
1925
|
+
* Runs after deleting a record.
|
|
1926
|
+
* @param id Deleted ID
|
|
975
1927
|
*/
|
|
976
1928
|
afterDelete?: (id: string) => Promise<void> | void;
|
|
977
1929
|
/**
|
|
978
|
-
*
|
|
979
|
-
* @param record
|
|
980
|
-
* @returns
|
|
1930
|
+
* Runs after getting a record by ID.
|
|
1931
|
+
* @param record Retrieved record (or null)
|
|
1932
|
+
* @returns Modified record
|
|
981
1933
|
*/
|
|
982
1934
|
afterFindById?: (record: T | null) => Promise<T | null> | T | null;
|
|
983
1935
|
/**
|
|
984
|
-
*
|
|
985
|
-
* @param result
|
|
986
|
-
* @returns
|
|
1936
|
+
* Runs after retrieving a paginated list.
|
|
1937
|
+
* @param result Paginated result
|
|
1938
|
+
* @returns Modified result
|
|
987
1939
|
*/
|
|
988
1940
|
afterFindAll?: (result: PaginatedResult<T>) => Promise<PaginatedResult<T>> | PaginatedResult<T>;
|
|
989
1941
|
}
|
|
990
1942
|
/**
|
|
991
|
-
*
|
|
1943
|
+
* Options for createEntityService
|
|
992
1944
|
*/
|
|
993
1945
|
interface CreateEntityServiceOptions<T = unknown> {
|
|
994
|
-
/** Hooks
|
|
1946
|
+
/** Hooks to customize behavior */
|
|
995
1947
|
hooks?: EntityServiceHooks<T>;
|
|
996
1948
|
}
|
|
997
1949
|
/**
|
|
998
|
-
*
|
|
1950
|
+
* Express route handler type for entity operations.
|
|
999
1951
|
*/
|
|
1000
1952
|
type EntityHandler = (req: Request, res: Response) => Promise<void>;
|
|
1001
1953
|
/**
|
|
1002
|
-
*
|
|
1954
|
+
* Entity controller with CRUD operation handlers.
|
|
1955
|
+
*
|
|
1956
|
+
* Generated by `createEntityController` with built-in CASL authorization.
|
|
1003
1957
|
*/
|
|
1004
1958
|
interface EntityController {
|
|
1959
|
+
/** GET / - List all records with pagination */
|
|
1005
1960
|
list: EntityHandler;
|
|
1961
|
+
/** GET /:id - Get single record by ID */
|
|
1006
1962
|
get: EntityHandler;
|
|
1963
|
+
/** POST / - Create new record (collection entities) */
|
|
1007
1964
|
create?: EntityHandler;
|
|
1965
|
+
/** PUT /:id - Update existing record */
|
|
1008
1966
|
update?: EntityHandler;
|
|
1967
|
+
/** DELETE /:id - Delete record */
|
|
1009
1968
|
delete?: EntityHandler;
|
|
1969
|
+
/** POST /:id/:action - Execute entity action */
|
|
1010
1970
|
execute?: EntityHandler;
|
|
1011
1971
|
}
|
|
1012
1972
|
/**
|
|
1013
|
-
*
|
|
1973
|
+
* Module context providing access to all Nexus services and utilities.
|
|
1974
|
+
*
|
|
1975
|
+
* Injected into module lifecycle functions (init, routes, migrate, seed).
|
|
1976
|
+
*
|
|
1977
|
+
* @example
|
|
1978
|
+
* ```typescript
|
|
1979
|
+
* const myModule: ModuleManifest = {
|
|
1980
|
+
* name: 'my-module',
|
|
1981
|
+
* routes: (ctx) => {
|
|
1982
|
+
* const router = ctx.createRouter()
|
|
1983
|
+
* router.get('/hello', (req, res) => res.json({ message: 'Hello!' }))
|
|
1984
|
+
* return router
|
|
1985
|
+
* }
|
|
1986
|
+
* }
|
|
1987
|
+
* ```
|
|
1014
1988
|
*/
|
|
1015
1989
|
interface ModuleContext {
|
|
1990
|
+
/** Knex database connection for queries and transactions */
|
|
1016
1991
|
db: Knex;
|
|
1992
|
+
/** Pino logger instance for structured logging */
|
|
1017
1993
|
logger: Logger;
|
|
1994
|
+
/** Helper utilities for migrations and common operations */
|
|
1018
1995
|
helpers: ContextHelpers;
|
|
1019
|
-
/**
|
|
1996
|
+
/** Cryptographic utilities (password hashing) */
|
|
1020
1997
|
crypto: ContextCrypto;
|
|
1021
|
-
/**
|
|
1998
|
+
/** Socket.IO API for real-time communication */
|
|
1022
1999
|
socket: ContextSocket;
|
|
1023
|
-
/**
|
|
2000
|
+
/** Engine API for module/plugin introspection */
|
|
1024
2001
|
engine: ContextEngine;
|
|
2002
|
+
/** Factory for creating Express routers */
|
|
1025
2003
|
createRouter: () => Router;
|
|
2004
|
+
/** Middleware factories (validate, rateLimit, etc.) */
|
|
1026
2005
|
middleware: ModuleMiddlewares;
|
|
1027
|
-
/**
|
|
2006
|
+
/** Resolved application configuration from env and config files */
|
|
1028
2007
|
config: Record<string, unknown>;
|
|
2008
|
+
/** HTTP error constructors for consistent error responses */
|
|
1029
2009
|
errors: {
|
|
2010
|
+
/** Generic application error with status code */
|
|
1030
2011
|
AppError: new (message: string, statusCode?: number, details?: unknown) => Error & {
|
|
1031
2012
|
details?: unknown;
|
|
1032
2013
|
};
|
|
2014
|
+
/** 404 Not Found error */
|
|
1033
2015
|
NotFoundError: new (message?: string) => Error;
|
|
2016
|
+
/** 401 Unauthorized error */
|
|
1034
2017
|
UnauthorizedError: new (message?: string) => Error;
|
|
2018
|
+
/** 403 Forbidden error */
|
|
1035
2019
|
ForbiddenError: new (message?: string) => Error;
|
|
2020
|
+
/** 409 Conflict error */
|
|
1036
2021
|
ConflictError: new (message?: string) => Error;
|
|
1037
|
-
|
|
1038
|
-
|
|
2022
|
+
/** 400 Validation error with field details */
|
|
2023
|
+
ValidationError: new (message?: string, details?: ValidationDetail[]) => Error & {
|
|
2024
|
+
details: ValidationDetail[];
|
|
1039
2025
|
};
|
|
1040
2026
|
};
|
|
2027
|
+
/** CASL abilities API for authorization */
|
|
1041
2028
|
abilities: ModuleAbilities;
|
|
1042
|
-
/**
|
|
2029
|
+
/** Event emitter for inter-module communication */
|
|
1043
2030
|
events: EventEmitterLike;
|
|
1044
|
-
/**
|
|
2031
|
+
/** Core services (users, mail, logger) */
|
|
1045
2032
|
services: CoreServices;
|
|
1046
|
-
/**
|
|
2033
|
+
/** Creates entity service based on definition type (auto-detects) */
|
|
1047
2034
|
createEntityService<T = unknown>(definition: EntityDefinition, options?: CreateEntityServiceOptions<T>): BaseEntityService<T>;
|
|
1048
|
-
/**
|
|
2035
|
+
/** Creates entity controller with CASL authorization and validation */
|
|
1049
2036
|
createEntityController(service: BaseEntityService, definition: EntityDefinition): EntityController;
|
|
1050
|
-
/**
|
|
2037
|
+
/** Creates Express router with standard CRUD routes */
|
|
1051
2038
|
createEntityRouter(controller: EntityController, definition: EntityDefinition): Router;
|
|
1052
2039
|
}
|
|
1053
2040
|
/**
|
|
1054
|
-
*
|
|
2041
|
+
* Module manifest defining a Nexus module.
|
|
2042
|
+
*
|
|
2043
|
+
* Modules are the core building blocks of Nexus applications.
|
|
2044
|
+
* Each module can define entities, routes, migrations, and services.
|
|
2045
|
+
*
|
|
2046
|
+
* @example
|
|
2047
|
+
* ```typescript
|
|
2048
|
+
* const postsModule: ModuleManifest = {
|
|
2049
|
+
* name: 'posts',
|
|
2050
|
+
* label: { en: 'Posts', es: 'Publicaciones' },
|
|
2051
|
+
* icon: 'mdi:post',
|
|
2052
|
+
* category: 'content',
|
|
2053
|
+
* definitions: [postsEntity, categoriesEntity],
|
|
2054
|
+
* routes: (ctx) => {
|
|
2055
|
+
* const router = ctx.createRouter()
|
|
2056
|
+
* // Custom routes here
|
|
2057
|
+
* return router
|
|
2058
|
+
* }
|
|
2059
|
+
* }
|
|
2060
|
+
* ```
|
|
1055
2061
|
*/
|
|
1056
2062
|
interface ModuleManifest {
|
|
1057
|
-
/**
|
|
2063
|
+
/** Unique module identifier (e.g., 'users', 'posts', 'cms') */
|
|
1058
2064
|
name: string;
|
|
1059
|
-
/**
|
|
1060
|
-
label?:
|
|
1061
|
-
/**
|
|
2065
|
+
/** Display name for UI (singular). Optional if module belongs to a plugin */
|
|
2066
|
+
label?: LocalizedString;
|
|
2067
|
+
/** Display name for UI (plural, used in menus and lists) */
|
|
2068
|
+
labelPlural?: LocalizedString;
|
|
2069
|
+
/** Iconify MDI icon identifier (e.g., 'mdi:account-group') */
|
|
1062
2070
|
icon?: string;
|
|
1063
|
-
/**
|
|
1064
|
-
description?:
|
|
1065
|
-
/**
|
|
2071
|
+
/** Module description for documentation */
|
|
2072
|
+
description?: LocalizedString;
|
|
2073
|
+
/** Module type: 'core' (built-in), 'plugin' (from plugin), 'auth-plugin', or 'custom' */
|
|
1066
2074
|
type?: 'core' | 'plugin' | 'auth-plugin' | 'custom';
|
|
1067
|
-
/**
|
|
2075
|
+
/** Category for sidebar grouping */
|
|
1068
2076
|
category: Category;
|
|
1069
|
-
/**
|
|
2077
|
+
/** Other module names that must be loaded before this one */
|
|
1070
2078
|
dependencies?: string[];
|
|
1071
|
-
/**
|
|
2079
|
+
/** Requirements that must be met to enable this module */
|
|
1072
2080
|
required?: ModuleRequirements;
|
|
1073
|
-
/**
|
|
2081
|
+
/** Database migration function. Usually auto-generated from entity definitions */
|
|
1074
2082
|
migrate?: (ctx: ModuleContext) => Promise<void>;
|
|
1075
|
-
/**
|
|
2083
|
+
/** Seed function for initial/reference data */
|
|
1076
2084
|
seed?: (ctx: ModuleContext) => Promise<void>;
|
|
1077
|
-
/**
|
|
2085
|
+
/** Initialization function called on app startup */
|
|
1078
2086
|
init?: (ctx: ModuleContext) => void;
|
|
1079
|
-
/** Factory
|
|
2087
|
+
/** Factory function returning Express router with custom routes */
|
|
1080
2088
|
routes?: (ctx: ModuleContext) => Router;
|
|
1081
|
-
/**
|
|
2089
|
+
/** API route prefix. Default: `/{name}` */
|
|
1082
2090
|
routePrefix?: string;
|
|
1083
|
-
/**
|
|
1084
|
-
* Definiciones de entidades (nuevo sistema unificado).
|
|
1085
|
-
* Single source of truth para BD, validación, UI y CASL.
|
|
1086
|
-
* Sus subjects se registran automáticamente.
|
|
1087
|
-
*/
|
|
2091
|
+
/** Entity definitions - single source of truth for DB, validation, UI, and CASL */
|
|
1088
2092
|
definitions?: EntityDefinition[];
|
|
1089
2093
|
}
|
|
1090
2094
|
/**
|
|
1091
|
-
*
|
|
2095
|
+
* Available categories for organizing modules in the UI sidebar.
|
|
2096
|
+
*
|
|
2097
|
+
* @see {@link CATEGORIES} for metadata (labels, icons, order)
|
|
1092
2098
|
*/
|
|
1093
|
-
type Category = 'content' | 'data' | '
|
|
2099
|
+
type Category = 'content' | 'data' | 'assets' | 'messaging' | 'jobs' | 'ai' | 'analytics' | 'integrations' | 'commerce' | 'security' | 'legal' | 'settings';
|
|
1094
2100
|
/**
|
|
1095
|
-
* Metadata
|
|
2101
|
+
* Metadata for a category including display information.
|
|
1096
2102
|
*/
|
|
1097
2103
|
interface CategoryMeta {
|
|
1098
|
-
label
|
|
2104
|
+
/** Localized display label */
|
|
2105
|
+
label: LocalizedString;
|
|
2106
|
+
/** Iconify MDI icon identifier */
|
|
1099
2107
|
icon: string;
|
|
2108
|
+
/** Sort order in sidebar (lower = first) */
|
|
1100
2109
|
order: number;
|
|
1101
2110
|
}
|
|
1102
2111
|
/**
|
|
1103
|
-
*
|
|
2112
|
+
* Registry of all categories with their metadata.
|
|
2113
|
+
*
|
|
2114
|
+
* Used by the UI to render the sidebar navigation.
|
|
1104
2115
|
*/
|
|
1105
2116
|
declare const CATEGORIES: Record<Category, CategoryMeta>;
|
|
1106
2117
|
/**
|
|
1107
|
-
*
|
|
2118
|
+
* Categories sorted by their display order for sidebar rendering.
|
|
1108
2119
|
*/
|
|
1109
2120
|
declare const CATEGORY_ORDER: Category[];
|
|
1110
2121
|
/**
|
|
1111
|
-
*
|
|
2122
|
+
* Plugin manifest defining a distributable Nexus plugin package.
|
|
2123
|
+
*
|
|
2124
|
+
* Plugins bundle one or more modules into a reusable package.
|
|
2125
|
+
* Distributed via npm (e.g., @gzl10/nexus-plugin-cms).
|
|
2126
|
+
*
|
|
2127
|
+
* @example
|
|
2128
|
+
* ```typescript
|
|
2129
|
+
* const cmsPlugin: PluginManifest = {
|
|
2130
|
+
* name: '@gzl10/nexus-plugin-cms',
|
|
2131
|
+
* code: 'CMS',
|
|
2132
|
+
* label: { en: 'CMS', es: 'CMS' },
|
|
2133
|
+
* category: 'content',
|
|
2134
|
+
* version: '1.0.0',
|
|
2135
|
+
* description: { en: 'Content Management System', es: 'Sistema de Gestión de Contenidos' },
|
|
2136
|
+
* modules: [postsModule, categoriesModule, tagsModule]
|
|
2137
|
+
* }
|
|
2138
|
+
* ```
|
|
1112
2139
|
*/
|
|
1113
2140
|
interface PluginManifest {
|
|
1114
|
-
/**
|
|
2141
|
+
/** Plugin npm package name (e.g., '@gzl10/nexus-plugin-cms') */
|
|
1115
2142
|
name: string;
|
|
1116
|
-
/**
|
|
2143
|
+
/** Short code prefix for tables (3-4 uppercase chars, e.g., 'CMS'). Applied to all modules */
|
|
1117
2144
|
code: string;
|
|
1118
|
-
/**
|
|
1119
|
-
label:
|
|
1120
|
-
/**
|
|
2145
|
+
/** Display label (singular). Inherited by modules without explicit label */
|
|
2146
|
+
label: LocalizedString;
|
|
2147
|
+
/** Display label (plural, for menus) */
|
|
2148
|
+
labelPlural?: LocalizedString;
|
|
2149
|
+
/** Iconify MDI icon. Inherited by modules without explicit icon */
|
|
1121
2150
|
icon?: string;
|
|
1122
|
-
/**
|
|
2151
|
+
/** Category for sidebar grouping. Inherited by modules without explicit category */
|
|
1123
2152
|
category: Category;
|
|
1124
|
-
/**
|
|
2153
|
+
/** Plugin version following SemVer (e.g., '1.0.0') */
|
|
1125
2154
|
version: string;
|
|
1126
|
-
/**
|
|
1127
|
-
description:
|
|
1128
|
-
/**
|
|
2155
|
+
/** Plugin description for documentation */
|
|
2156
|
+
description: LocalizedString;
|
|
2157
|
+
/** Modules bundled in this plugin */
|
|
1129
2158
|
modules: ModuleManifest[];
|
|
1130
2159
|
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Serializable field definition for API responses.
|
|
2162
|
+
*
|
|
2163
|
+
* Unlike {@link FieldDefinition}, this contains no functions (ConditionalBoolean
|
|
2164
|
+
* resolved to static boolean). Some fields may be omitted for security.
|
|
2165
|
+
*
|
|
2166
|
+
* @see {@link FieldDefinition} for the full server-side definition
|
|
2167
|
+
*/
|
|
2168
|
+
interface FieldDefinitionDTO {
|
|
2169
|
+
/** Field identifier */
|
|
2170
|
+
name: string;
|
|
2171
|
+
/** Display label */
|
|
2172
|
+
label: LocalizedString;
|
|
2173
|
+
/** Input type for form rendering */
|
|
2174
|
+
input: InputType;
|
|
2175
|
+
/** Placeholder text */
|
|
2176
|
+
placeholder?: LocalizedString;
|
|
2177
|
+
/** Help text */
|
|
2178
|
+
hint?: LocalizedString;
|
|
2179
|
+
/** Whether field is hidden (resolved from ConditionalBoolean) */
|
|
2180
|
+
hidden?: boolean;
|
|
2181
|
+
/** Whether field is disabled (resolved from ConditionalBoolean) */
|
|
2182
|
+
disabled?: boolean;
|
|
2183
|
+
/** Whether field is required (resolved from ConditionalBoolean) */
|
|
2184
|
+
required?: boolean;
|
|
2185
|
+
/** Default value for form creation */
|
|
2186
|
+
defaultValue?: unknown;
|
|
2187
|
+
/** Database config (may be partially omitted for security) */
|
|
2188
|
+
db?: {
|
|
2189
|
+
type: string;
|
|
2190
|
+
nullable?: boolean;
|
|
2191
|
+
default?: unknown;
|
|
2192
|
+
unique?: boolean;
|
|
2193
|
+
index?: boolean;
|
|
2194
|
+
};
|
|
2195
|
+
/** Relationship config (table name may be omitted for security) */
|
|
2196
|
+
relation?: {
|
|
2197
|
+
table?: string;
|
|
2198
|
+
column?: string;
|
|
2199
|
+
labelField?: string;
|
|
2200
|
+
onDelete?: string;
|
|
2201
|
+
};
|
|
2202
|
+
/** Validation rules */
|
|
2203
|
+
validation?: {
|
|
2204
|
+
min?: number;
|
|
2205
|
+
max?: number;
|
|
2206
|
+
pattern?: string;
|
|
2207
|
+
format?: string;
|
|
2208
|
+
enum?: string[];
|
|
2209
|
+
};
|
|
2210
|
+
/** Select/dropdown options configuration */
|
|
2211
|
+
options?: {
|
|
2212
|
+
endpoint?: string;
|
|
2213
|
+
valueField?: string;
|
|
2214
|
+
labelField?: string;
|
|
2215
|
+
static?: Array<{
|
|
2216
|
+
value: string;
|
|
2217
|
+
label: LocalizedString;
|
|
2218
|
+
}>;
|
|
2219
|
+
allowCreate?: boolean;
|
|
2220
|
+
};
|
|
2221
|
+
/** Storage configuration for file uploads */
|
|
2222
|
+
storage?: {
|
|
2223
|
+
accept?: string;
|
|
2224
|
+
maxSize?: number;
|
|
2225
|
+
maxFiles?: number;
|
|
2226
|
+
folder?: string;
|
|
2227
|
+
thumbnails?: Array<{
|
|
2228
|
+
width: number;
|
|
2229
|
+
height: number;
|
|
2230
|
+
}>;
|
|
2231
|
+
dedupe?: boolean;
|
|
2232
|
+
};
|
|
2233
|
+
/** Field metadata */
|
|
2234
|
+
meta?: FieldMeta;
|
|
2235
|
+
/** Props for input components */
|
|
2236
|
+
inputProps?: Record<string, unknown>;
|
|
2237
|
+
/** Props for display components */
|
|
2238
|
+
displayProps?: Record<string, unknown>;
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Serializable entity definition for API responses.
|
|
2242
|
+
*
|
|
2243
|
+
* Flattened structure without discriminated union.
|
|
2244
|
+
* Sensitive information may be omitted for security.
|
|
2245
|
+
*
|
|
2246
|
+
* @see {@link EntityDefinition} for the full server-side definition
|
|
2247
|
+
*/
|
|
2248
|
+
interface EntityDefinitionDTO {
|
|
2249
|
+
/** Unique entity identifier (auto-generated at registration) */
|
|
2250
|
+
id: string;
|
|
2251
|
+
/** Table name (may be omitted for security) */
|
|
2252
|
+
table?: string;
|
|
2253
|
+
/** Key for single entities */
|
|
2254
|
+
key?: string;
|
|
2255
|
+
/** Entity type */
|
|
2256
|
+
type: EntityType;
|
|
2257
|
+
/** Display label (singular) */
|
|
2258
|
+
label: LocalizedString;
|
|
2259
|
+
/** Display label (plural) */
|
|
2260
|
+
labelPlural?: LocalizedString;
|
|
2261
|
+
/** Field used as display label */
|
|
2262
|
+
labelField?: string;
|
|
2263
|
+
/** Iconify icon */
|
|
2264
|
+
icon?: string;
|
|
2265
|
+
/** API route prefix */
|
|
2266
|
+
routePrefix?: string;
|
|
2267
|
+
/** UI sort order */
|
|
2268
|
+
order?: number;
|
|
2269
|
+
/** Field definitions */
|
|
2270
|
+
fields: Record<string, FieldDefinitionDTO>;
|
|
2271
|
+
/** Total field count */
|
|
2272
|
+
fieldsCount: number;
|
|
2273
|
+
/** Has created_at/updated_at columns */
|
|
2274
|
+
hasTimestamps: boolean;
|
|
2275
|
+
/** Has created_by/updated_by columns */
|
|
2276
|
+
hasAudit: boolean;
|
|
2277
|
+
/** CASL subject name */
|
|
2278
|
+
caslSubject?: string;
|
|
2279
|
+
/** Returns single record instead of array */
|
|
2280
|
+
isSingle?: boolean;
|
|
2281
|
+
/** UI display mode */
|
|
2282
|
+
displayMode?: DisplayMode;
|
|
2283
|
+
/** Show create button in UI */
|
|
2284
|
+
allowCreate?: boolean;
|
|
2285
|
+
/** Allow editing in UI */
|
|
2286
|
+
allowEdit?: boolean;
|
|
2287
|
+
/** Hidden from UI sidebar */
|
|
2288
|
+
hidden?: boolean;
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Serializable module for API responses.
|
|
2292
|
+
*/
|
|
2293
|
+
interface ModuleDTO {
|
|
2294
|
+
/** Module identifier */
|
|
2295
|
+
name: string;
|
|
2296
|
+
/** Display label */
|
|
2297
|
+
label: LocalizedString;
|
|
2298
|
+
/** Display label (plural) */
|
|
2299
|
+
labelPlural?: LocalizedString;
|
|
2300
|
+
/** Iconify icon */
|
|
2301
|
+
icon?: string;
|
|
2302
|
+
/** Module description */
|
|
2303
|
+
description?: LocalizedString;
|
|
2304
|
+
/** Module type */
|
|
2305
|
+
type: string;
|
|
2306
|
+
/** Sidebar category */
|
|
2307
|
+
category?: Category;
|
|
2308
|
+
/** Module dependencies */
|
|
2309
|
+
dependencies: string[];
|
|
2310
|
+
/** API route prefix */
|
|
2311
|
+
routePrefix: string;
|
|
2312
|
+
/** CASL subjects defined by this module */
|
|
2313
|
+
subjects: string[];
|
|
2314
|
+
/** Entity definitions */
|
|
2315
|
+
definitions: EntityDefinitionDTO[];
|
|
2316
|
+
/** Has custom routes */
|
|
2317
|
+
hasRoutes: boolean;
|
|
2318
|
+
/** Has migrations */
|
|
2319
|
+
hasMigrate: boolean;
|
|
2320
|
+
/** Has seed data */
|
|
2321
|
+
hasSeed: boolean;
|
|
2322
|
+
/** Has init function */
|
|
2323
|
+
hasInit: boolean;
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Serializable plugin for API responses.
|
|
2327
|
+
*/
|
|
2328
|
+
interface PluginDTO {
|
|
2329
|
+
/** Plugin package name */
|
|
2330
|
+
name: string;
|
|
2331
|
+
/** Plugin code prefix */
|
|
2332
|
+
code: string;
|
|
2333
|
+
/** Display label */
|
|
2334
|
+
label: LocalizedString;
|
|
2335
|
+
/** Display label (plural) */
|
|
2336
|
+
labelPlural?: LocalizedString;
|
|
2337
|
+
/** Iconify icon */
|
|
2338
|
+
icon?: string;
|
|
2339
|
+
/** Sidebar category */
|
|
2340
|
+
category?: Category;
|
|
2341
|
+
/** Plugin version */
|
|
2342
|
+
version: string;
|
|
2343
|
+
/** Plugin description */
|
|
2344
|
+
description: LocalizedString;
|
|
2345
|
+
/** Modules in this plugin */
|
|
2346
|
+
modules: ModuleDTO[];
|
|
2347
|
+
}
|
|
2348
|
+
/**
|
|
2349
|
+
* Application manifest DTO (core or user app).
|
|
2350
|
+
*/
|
|
2351
|
+
interface ManifestDTO {
|
|
2352
|
+
/** Application name */
|
|
2353
|
+
name: string;
|
|
2354
|
+
/** Application version */
|
|
2355
|
+
version: string;
|
|
2356
|
+
/** Registered modules */
|
|
2357
|
+
modules: ModuleDTO[];
|
|
2358
|
+
/** Registered plugins */
|
|
2359
|
+
plugins?: PluginDTO[];
|
|
2360
|
+
}
|
|
2361
|
+
/**
|
|
2362
|
+
* Combined manifest DTO with both core and user manifests.
|
|
2363
|
+
*/
|
|
2364
|
+
interface CombinedManifestDTO {
|
|
2365
|
+
/** Core nexus-backend manifest */
|
|
2366
|
+
core: ManifestDTO;
|
|
2367
|
+
/** User project manifest (null in standalone mode) */
|
|
2368
|
+
user: ManifestDTO | null;
|
|
2369
|
+
/** Whether running with a user project */
|
|
2370
|
+
hasUserApp: boolean;
|
|
2371
|
+
}
|
|
1131
2372
|
|
|
1132
|
-
export { type AbilityLike, type ActionEntityDefinition, type ActionEntityService, type AuthRequest, type BaseEntityService, type BaseMailService, type BaseRolesService, type BaseUser, type BaseUsersService, CATEGORIES, CATEGORY_ORDER, type CaslAction, type Category, type CategoryMeta, type CollectionEntityDefinition, type CollectionEntityService, type ComputedEntityDefinition, type ComputedEntityService, type ConfigEntityDefinition, type ConfigEntityService, type ContextCrypto, type ContextEngine, type ContextHelpers, type ContextSocket, type CoreServices, type CreateEntityServiceOptions, type DbType, type EntityAction, type EntityCaslConfig, type EntityController, type EntityDefinition, type EntityHandler, type EntityIndex, type EntityQuery, type EntityServiceHooks, type EntityType, type EventEmitterLike, type EventEntityDefinition, type EventEntityService, type ExternalEntityDefinition, type ExternalEntityService, type
|
|
2373
|
+
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 TempEntityDefinition, type TempEntityService, type ValidateSchemas, type ValidationDetail, type ValidationSchema, type ViewEntityDefinition, type ViewEntityService, type VirtualEntityDefinition, type VirtualEntityService, getCountLabel, getEntityName, getEntitySubject, hasTable, isPersistentEntity, isSingletonEntity, resolveLocalized };
|