@gzl10/nexus-sdk 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -3
- package/dist/index.d.ts +214 -97
- package/dist/index.js +235 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,17 +98,131 @@ export const projectPlugin: PluginManifest = {
|
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
## EntityDefinition (v0.2.0+)
|
|
102
|
+
|
|
103
|
+
Define entities as single source of truth for DB, validation, UI, and CASL:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import type { EntityDefinition } from '@gzl10/nexus-sdk'
|
|
107
|
+
|
|
108
|
+
export const postEntity: EntityDefinition = {
|
|
109
|
+
table: 'cms_posts',
|
|
110
|
+
label: 'Posts',
|
|
111
|
+
labelField: 'title',
|
|
112
|
+
timestamps: true,
|
|
113
|
+
audit: true,
|
|
114
|
+
|
|
115
|
+
fields: {
|
|
116
|
+
id: {
|
|
117
|
+
name: 'id',
|
|
118
|
+
label: 'ID',
|
|
119
|
+
input: 'hidden',
|
|
120
|
+
db: { type: 'uuid', nullable: false, defaultFn: 'uuid' }
|
|
121
|
+
},
|
|
122
|
+
title: {
|
|
123
|
+
name: 'title',
|
|
124
|
+
label: 'Title',
|
|
125
|
+
input: 'text',
|
|
126
|
+
db: { type: 'string', size: 200, nullable: false },
|
|
127
|
+
validation: { required: true, min: 3, max: 200 },
|
|
128
|
+
meta: { searchable: true, sortable: true }
|
|
129
|
+
},
|
|
130
|
+
content: {
|
|
131
|
+
name: 'content',
|
|
132
|
+
label: 'Content',
|
|
133
|
+
input: 'markdown',
|
|
134
|
+
db: { type: 'text', nullable: true }
|
|
135
|
+
},
|
|
136
|
+
status: {
|
|
137
|
+
name: 'status',
|
|
138
|
+
label: 'Status',
|
|
139
|
+
input: 'select',
|
|
140
|
+
db: { type: 'string', nullable: false, default: 'draft' },
|
|
141
|
+
validation: { enum: ['draft', 'published', 'archived'] },
|
|
142
|
+
options: {
|
|
143
|
+
static: [
|
|
144
|
+
{ value: 'draft', label: 'Draft' },
|
|
145
|
+
{ value: 'published', label: 'Published' },
|
|
146
|
+
{ value: 'archived', label: 'Archived' }
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
casl: {
|
|
153
|
+
ownership: { field: 'created_by' },
|
|
154
|
+
permissions: {
|
|
155
|
+
ADMIN: { actions: ['manage'] },
|
|
156
|
+
EDITOR: { actions: ['create', 'read', 'update'] },
|
|
157
|
+
VIEWER: { actions: ['read'] }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Code Generators
|
|
164
|
+
|
|
165
|
+
Generate migrations, Zod schemas, TypeScript models, and CASL permissions:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import {
|
|
169
|
+
generateMigration,
|
|
170
|
+
generateZodSchema,
|
|
171
|
+
generateModel,
|
|
172
|
+
generateCaslPermissions
|
|
173
|
+
} from '@gzl10/nexus-sdk'
|
|
174
|
+
|
|
175
|
+
// Generate Knex migration code
|
|
176
|
+
const migrationCode = generateMigration(postEntity)
|
|
177
|
+
|
|
178
|
+
// Generate Zod validation schemas
|
|
179
|
+
const zodCode = generateZodSchema(postEntity)
|
|
180
|
+
// Creates: createPostSchema, updatePostSchema, postParamsSchema, postQuerySchema
|
|
181
|
+
|
|
182
|
+
// Generate TypeScript interface
|
|
183
|
+
const modelCode = generateModel(postEntity)
|
|
184
|
+
// Creates: export interface Post { ... }
|
|
185
|
+
|
|
186
|
+
// Generate CASL permissions for seeding
|
|
187
|
+
const permissions = generateCaslPermissions(postEntity)
|
|
188
|
+
// Returns: Array<{ role, action, subject, conditions, fields, inverted }>
|
|
189
|
+
```
|
|
190
|
+
|
|
101
191
|
## Main Types
|
|
102
192
|
|
|
103
193
|
### Manifests & Context
|
|
104
194
|
|
|
105
195
|
| Type | Description |
|
|
106
196
|
|------|-------------|
|
|
107
|
-
| `ModuleManifest` | Defines a module: routes, migrations, seeds,
|
|
197
|
+
| `ModuleManifest` | Defines a module: routes, migrations, seeds, entities |
|
|
108
198
|
| `PluginManifest` | Groups modules under a plugin with shared metadata |
|
|
109
199
|
| `ModuleContext` | Context injected by Nexus: `db`, `logger`, `helpers`, `services`, `abilities` |
|
|
110
|
-
|
|
111
|
-
|
|
200
|
+
|
|
201
|
+
### Entity Definition System
|
|
202
|
+
|
|
203
|
+
| Type | Description |
|
|
204
|
+
|------|-------------|
|
|
205
|
+
| `EntityDefinition` | Complete entity definition (DB, UI, validation, CASL) |
|
|
206
|
+
| `FieldDefinition` | Field configuration with DB, UI, and validation settings |
|
|
207
|
+
| `FieldDbConfig` | Database column configuration |
|
|
208
|
+
| `FieldRelation` | Foreign key configuration |
|
|
209
|
+
| `EntityCaslConfig` | CASL authorization configuration |
|
|
210
|
+
|
|
211
|
+
### Entity Types (Discriminated Union)
|
|
212
|
+
|
|
213
|
+
| Type | Description |
|
|
214
|
+
|------|-------------|
|
|
215
|
+
| `CollectionEntity` | Full CRUD (users, posts) |
|
|
216
|
+
| `ReferenceEntity` | Catalogs with admin CRUD (countries) |
|
|
217
|
+
| `SingleEntity` | Singleton, update/read only (site_config) |
|
|
218
|
+
| `ConfigEntity` | Per-module config, singleton |
|
|
219
|
+
| `ExternalEntity` | Read-only from external systems |
|
|
220
|
+
| `VirtualEntity` | Orchestration of multiple sources |
|
|
221
|
+
| `ComputedEntity` | KPIs, calculated stats |
|
|
222
|
+
| `ViewEntity` | Optimized read projections |
|
|
223
|
+
| `EventEntity` | Audit logs, append-only |
|
|
224
|
+
| `TempEntity` | TTL-based (cache, sessions) |
|
|
225
|
+
| `ActionEntity` | Commands without persistence |
|
|
112
226
|
|
|
113
227
|
### Request & Auth
|
|
114
228
|
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,27 @@ import { Logger } from 'pino';
|
|
|
11
11
|
* que pueden ser escritos a archivos o usados en runtime.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Entidades que persisten en BD local con tabla propia
|
|
16
|
+
* Excluye: action, external, virtual, computed, single (usa tabla compartida)
|
|
17
|
+
*/
|
|
18
|
+
type PersistentEntityDefinition = CollectionEntityDefinition | ReferenceEntityDefinition | EventEntityDefinition | ConfigEntityDefinition | TempEntityDefinition | ViewEntityDefinition;
|
|
19
|
+
/**
|
|
20
|
+
* Entidades sin persistencia local (read-only o sin BD)
|
|
21
|
+
*/
|
|
22
|
+
type NonPersistentEntityDefinition = ActionEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition | ComputedEntityDefinition;
|
|
23
|
+
/**
|
|
24
|
+
* Type guard para verificar si una entidad persiste en BD local con tabla propia
|
|
25
|
+
*/
|
|
26
|
+
declare function isPersistentEntity(entity: EntityDefinition): entity is PersistentEntityDefinition;
|
|
27
|
+
/**
|
|
28
|
+
* Type guard para verificar si es un singleton (usa tabla compartida sys_settings)
|
|
29
|
+
*/
|
|
30
|
+
declare function isSingletonEntity(entity: EntityDefinition): entity is SingleEntityDefinition;
|
|
31
|
+
/**
|
|
32
|
+
* Type guard para verificar si una entidad tiene tabla propia (para migraciones)
|
|
33
|
+
*/
|
|
34
|
+
declare function hasTable(entity: EntityDefinition): entity is PersistentEntityDefinition;
|
|
14
35
|
/**
|
|
15
36
|
* Genera código de migración Knex desde EntityDefinition
|
|
16
37
|
*
|
|
@@ -24,7 +45,7 @@ import { Logger } from 'pino';
|
|
|
24
45
|
* // ...
|
|
25
46
|
* // }
|
|
26
47
|
*/
|
|
27
|
-
declare function generateMigration(entity:
|
|
48
|
+
declare function generateMigration(entity: PersistentEntityDefinition): string;
|
|
28
49
|
/**
|
|
29
50
|
* Genera código de schemas Zod desde EntityDefinition
|
|
30
51
|
*
|
|
@@ -32,7 +53,7 @@ declare function generateMigration(entity: EntityDefinition): string;
|
|
|
32
53
|
* const code = generateZodSchema(postEntity)
|
|
33
54
|
* // Genera createPostSchema, updatePostSchema, postParamsSchema, postQuerySchema
|
|
34
55
|
*/
|
|
35
|
-
declare function generateZodSchema(entity:
|
|
56
|
+
declare function generateZodSchema(entity: PersistentEntityDefinition): string;
|
|
36
57
|
/**
|
|
37
58
|
* Genera interface TypeScript desde EntityDefinition
|
|
38
59
|
*
|
|
@@ -45,7 +66,16 @@ declare function generateZodSchema(entity: EntityDefinition): string;
|
|
|
45
66
|
* // ...
|
|
46
67
|
* // }
|
|
47
68
|
*/
|
|
48
|
-
declare function generateModel(entity:
|
|
69
|
+
declare function generateModel(entity: PersistentEntityDefinition): string;
|
|
70
|
+
/**
|
|
71
|
+
* Genera schema Zod para entidades read-only
|
|
72
|
+
* Solo genera el schema de output, no create/update
|
|
73
|
+
*/
|
|
74
|
+
declare function generateReadOnlySchema(entity: ComputedEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition): string;
|
|
75
|
+
/**
|
|
76
|
+
* Genera interface TypeScript para entidades read-only
|
|
77
|
+
*/
|
|
78
|
+
declare function generateReadOnlyModel(entity: ComputedEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition): string;
|
|
49
79
|
/**
|
|
50
80
|
* Estructura de permiso generado para insertar en BD
|
|
51
81
|
*/
|
|
@@ -64,7 +94,7 @@ interface GeneratedPermission {
|
|
|
64
94
|
* const permissions = generateCaslPermissions(postEntity)
|
|
65
95
|
* // Genera array de permisos para insertar en rol_role_permissions
|
|
66
96
|
*/
|
|
67
|
-
declare function generateCaslPermissions(entity:
|
|
97
|
+
declare function generateCaslPermissions(entity: PersistentEntityDefinition): GeneratedPermission[];
|
|
68
98
|
/**
|
|
69
99
|
* Genera código de seed para permisos CASL
|
|
70
100
|
*
|
|
@@ -76,7 +106,30 @@ declare function generateCaslSeed(entities: EntityDefinition[]): string;
|
|
|
76
106
|
/**
|
|
77
107
|
* Obtiene el subject CASL de una entidad
|
|
78
108
|
*/
|
|
79
|
-
declare function getEntitySubject(entity:
|
|
109
|
+
declare function getEntitySubject(entity: PersistentEntityDefinition): string;
|
|
110
|
+
/**
|
|
111
|
+
* Obtiene el nombre de una entidad en PascalCase singular
|
|
112
|
+
* 'cms_posts' → 'Post', 'rol_role_permissions' → 'RolePermission'
|
|
113
|
+
*/
|
|
114
|
+
declare function getEntityName(entity: EntityDefinition): string;
|
|
115
|
+
/**
|
|
116
|
+
* Genera archivo completo de schemas Zod para múltiples entidades
|
|
117
|
+
* Consolida todas las entidades de un módulo en un solo archivo
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* const code = generateSchemasFile([userEntity, roleEntity])
|
|
121
|
+
* writeFileSync('users.schemas.ts', code)
|
|
122
|
+
*/
|
|
123
|
+
declare function generateSchemasFile(definitions: EntityDefinition[]): string;
|
|
124
|
+
/**
|
|
125
|
+
* Genera archivo completo de modelos TypeScript para múltiples entidades
|
|
126
|
+
* Consolida todas las entidades de un módulo en un solo archivo
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* const code = generateModelsFile([userEntity, roleEntity])
|
|
130
|
+
* writeFileSync('users.models.ts', code)
|
|
131
|
+
*/
|
|
132
|
+
declare function generateModelsFile(definitions: EntityDefinition[]): string;
|
|
80
133
|
|
|
81
134
|
/**
|
|
82
135
|
* @gzl10/nexus-sdk
|
|
@@ -85,6 +138,23 @@ declare function getEntitySubject(entity: EntityDefinition): string;
|
|
|
85
138
|
* Use this package to define plugin/module manifests without
|
|
86
139
|
* depending on the full @gzl10/nexus-backend package.
|
|
87
140
|
*/
|
|
141
|
+
/**
|
|
142
|
+
* Unión discriminada de todos los tipos de entidad
|
|
143
|
+
*
|
|
144
|
+
* | Type | Persistencia | CRUD | Uso principal |
|
|
145
|
+
* |------------|--------------|-----------------|----------------------------------|
|
|
146
|
+
* | collection | Sí (BD) | Completo | Datos de negocio (users, posts) |
|
|
147
|
+
* | single | Sí (BD) | Update/Read | Config global (site_config) |
|
|
148
|
+
* | external | No | Read | Datos externos (stripe_customers)|
|
|
149
|
+
* | virtual | No | Read | Orquestación (unified_customers) |
|
|
150
|
+
* | computed | No/opcional | Read | KPIs, estadísticas |
|
|
151
|
+
* | view | Sí/virtual | Read | Lectura optimizada (projections) |
|
|
152
|
+
* | reference | Sí | Read (admin) | Catálogos (countries, currencies)|
|
|
153
|
+
* | config | Sí | Update/Read | Config por módulo |
|
|
154
|
+
* | event | Sí | Append | Auditoría (audit_logs) |
|
|
155
|
+
* | temp | No (TTL) | Read/Write | Cache, sesiones (otp_codes) |
|
|
156
|
+
* | action | No | Execute | Operaciones, workflows |
|
|
157
|
+
*/
|
|
88
158
|
|
|
89
159
|
type KnexCreateTableBuilder = Knex.CreateTableBuilder;
|
|
90
160
|
type KnexAlterTableBuilder = Knex.AlterTableBuilder;
|
|
@@ -320,134 +390,188 @@ interface EntityCaslConfig {
|
|
|
320
390
|
sensitiveFields?: string[];
|
|
321
391
|
}
|
|
322
392
|
/**
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
* Contiene toda la información necesaria para:
|
|
326
|
-
* - Generar migraciones Knex
|
|
327
|
-
* - Generar schemas Zod
|
|
328
|
-
* - Generar tipos TypeScript
|
|
329
|
-
* - Configurar UI CRUD
|
|
330
|
-
* - Configurar permisos CASL
|
|
393
|
+
* Propiedades base compartidas por todas las EntityDefinition
|
|
331
394
|
*/
|
|
332
|
-
interface
|
|
395
|
+
interface BaseEntityDefinition {
|
|
396
|
+
/** Nombre de tabla en BD (con prefijo: 'cms_posts') */
|
|
333
397
|
table: string;
|
|
398
|
+
/** Nombre para mostrar en UI */
|
|
334
399
|
label: string;
|
|
335
|
-
|
|
400
|
+
/** Definición de campos */
|
|
336
401
|
fields: Record<string, FieldDefinition>;
|
|
337
|
-
|
|
338
|
-
audit?: boolean;
|
|
339
|
-
indexes?: EntityIndex[];
|
|
402
|
+
/** Autorización CASL */
|
|
340
403
|
casl?: EntityCaslConfig;
|
|
341
404
|
}
|
|
342
|
-
/**
|
|
343
|
-
* Propiedades base compartidas por todas las entidades
|
|
344
|
-
*/
|
|
345
|
-
interface BaseEntity {
|
|
346
|
-
name: string;
|
|
347
|
-
label: string;
|
|
348
|
-
}
|
|
349
405
|
/**
|
|
350
406
|
* Entidad de colección - CRUD completo (users, posts, orders)
|
|
351
407
|
* Default cuando no se especifica type
|
|
352
408
|
*/
|
|
353
|
-
interface
|
|
409
|
+
interface CollectionEntityDefinition extends BaseEntityDefinition {
|
|
354
410
|
type?: 'collection';
|
|
355
|
-
|
|
356
|
-
formFields?: Record<string, FormField>;
|
|
411
|
+
/** Campo para mostrar en selects/referencias */
|
|
357
412
|
labelField: string;
|
|
358
|
-
|
|
359
|
-
|
|
413
|
+
/** Añadir created_at, updated_at */
|
|
414
|
+
timestamps?: boolean;
|
|
415
|
+
/** Añadir created_by, updated_by */
|
|
416
|
+
audit?: boolean;
|
|
417
|
+
/** Índices compuestos */
|
|
418
|
+
indexes?: EntityIndex[];
|
|
360
419
|
}
|
|
361
420
|
/**
|
|
362
|
-
* Entidad
|
|
421
|
+
* Entidad singleton - Un solo registro en tabla compartida sys_settings
|
|
422
|
+
* Usa key-value: key identifica el registro, value es JSON con la estructura de fields
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* // site_config singleton
|
|
426
|
+
* { type: 'single', key: 'site_config', label: 'Site Config', fields: { siteName: {...}, logo: {...} } }
|
|
427
|
+
* // Se guarda en sys_settings como: { key: 'site_config', value: { siteName: 'Mi App', logo: '...' } }
|
|
363
428
|
*/
|
|
364
|
-
interface
|
|
365
|
-
type: '
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
429
|
+
interface SingleEntityDefinition {
|
|
430
|
+
type: 'single';
|
|
431
|
+
/** Clave única en tabla sys_settings (ej: 'site_config', 'smtp_settings') */
|
|
432
|
+
key: string;
|
|
433
|
+
/** Nombre para mostrar en UI */
|
|
434
|
+
label: string;
|
|
435
|
+
/** Definición de campos (estructura del JSON value) */
|
|
436
|
+
fields: Record<string, FieldDefinition>;
|
|
437
|
+
/** Autorización CASL */
|
|
438
|
+
casl?: EntityCaslConfig;
|
|
371
439
|
}
|
|
372
440
|
/**
|
|
373
|
-
* Entidad
|
|
441
|
+
* Entidad de referencia - Catálogos con CRUD admin (countries, currencies)
|
|
374
442
|
*/
|
|
375
|
-
interface
|
|
376
|
-
type: '
|
|
377
|
-
|
|
378
|
-
formFields?: Record<string, FormField>;
|
|
443
|
+
interface ReferenceEntityDefinition extends BaseEntityDefinition {
|
|
444
|
+
type: 'reference';
|
|
445
|
+
/** Campo para mostrar en selects/referencias */
|
|
379
446
|
labelField: string;
|
|
380
|
-
|
|
381
|
-
|
|
447
|
+
/** Añadir created_at, updated_at */
|
|
448
|
+
timestamps?: boolean;
|
|
449
|
+
/** Índices compuestos */
|
|
450
|
+
indexes?: EntityIndex[];
|
|
382
451
|
}
|
|
383
452
|
/**
|
|
384
|
-
* Entidad
|
|
453
|
+
* Entidad de eventos - Logs de auditoría, append-only
|
|
385
454
|
*/
|
|
386
|
-
interface
|
|
387
|
-
type: '
|
|
388
|
-
|
|
455
|
+
interface EventEntityDefinition extends BaseEntityDefinition {
|
|
456
|
+
type: 'event';
|
|
457
|
+
/** Campo para mostrar en listas */
|
|
458
|
+
labelField: string;
|
|
459
|
+
/** Siempre true para eventos */
|
|
460
|
+
timestamps: true;
|
|
389
461
|
}
|
|
390
462
|
/**
|
|
391
|
-
* Entidad de
|
|
463
|
+
* Entidad de acción - Comandos/operaciones sin persistencia
|
|
392
464
|
*/
|
|
393
|
-
interface
|
|
394
|
-
type: '
|
|
395
|
-
|
|
465
|
+
interface ActionEntityDefinition {
|
|
466
|
+
type: 'action';
|
|
467
|
+
/** Nombre para mostrar en UI */
|
|
468
|
+
label: string;
|
|
469
|
+
/** Solo campos de formulario, sin BD */
|
|
470
|
+
fields: Record<string, FieldDefinition>;
|
|
396
471
|
}
|
|
397
472
|
/**
|
|
398
|
-
* Entidad externa -
|
|
473
|
+
* Entidad externa - Datos de APIs externas (stripe_customers, github_repos)
|
|
474
|
+
* Read-only, sin persistencia local
|
|
399
475
|
*/
|
|
400
|
-
interface
|
|
476
|
+
interface ExternalEntityDefinition {
|
|
401
477
|
type: 'external';
|
|
402
|
-
|
|
478
|
+
/** Nombre para mostrar en UI */
|
|
479
|
+
label: string;
|
|
480
|
+
/** Campo para mostrar en selects/referencias */
|
|
403
481
|
labelField: string;
|
|
404
|
-
|
|
482
|
+
/** Definición de campos (estructura esperada del API externo) */
|
|
483
|
+
fields: Record<string, FieldDefinition>;
|
|
484
|
+
/** Configuración del origen externo */
|
|
485
|
+
source: {
|
|
486
|
+
/** Tipo de origen */
|
|
487
|
+
provider: string;
|
|
488
|
+
/** Endpoint o recurso */
|
|
489
|
+
endpoint?: string;
|
|
490
|
+
};
|
|
491
|
+
/** Autorización CASL */
|
|
492
|
+
casl?: EntityCaslConfig;
|
|
405
493
|
}
|
|
406
494
|
/**
|
|
407
495
|
* Entidad virtual - Orquestación de múltiples fuentes (unified_customers)
|
|
496
|
+
* Read-only, combina datos de varias entidades
|
|
408
497
|
*/
|
|
409
|
-
interface
|
|
498
|
+
interface VirtualEntityDefinition {
|
|
410
499
|
type: 'virtual';
|
|
411
|
-
|
|
500
|
+
/** Nombre para mostrar en UI */
|
|
501
|
+
label: string;
|
|
502
|
+
/** Campo para mostrar en selects/referencias */
|
|
412
503
|
labelField: string;
|
|
413
|
-
|
|
504
|
+
/** Definición de campos (esquema unificado) */
|
|
505
|
+
fields: Record<string, FieldDefinition>;
|
|
506
|
+
/** Fuentes de datos que se combinan */
|
|
507
|
+
sources: string[];
|
|
508
|
+
/** Autorización CASL */
|
|
509
|
+
casl?: EntityCaslConfig;
|
|
414
510
|
}
|
|
415
511
|
/**
|
|
416
|
-
* Entidad
|
|
512
|
+
* Entidad computed - KPIs, estadísticas, métricas calculadas
|
|
513
|
+
* Read-only, puede cachear opcionalmente
|
|
417
514
|
*/
|
|
418
|
-
interface
|
|
515
|
+
interface ComputedEntityDefinition {
|
|
419
516
|
type: 'computed';
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
517
|
+
/** Nombre para mostrar en UI */
|
|
518
|
+
label: string;
|
|
519
|
+
/** Campo para mostrar en selects/referencias */
|
|
520
|
+
labelField?: string;
|
|
521
|
+
/** Definición de campos (estructura del resultado) */
|
|
522
|
+
fields: Record<string, FieldDefinition>;
|
|
523
|
+
/** Tiempo de cache en segundos (0 = sin cache) */
|
|
524
|
+
cacheTtl?: number;
|
|
525
|
+
/** Autorización CASL */
|
|
526
|
+
casl?: EntityCaslConfig;
|
|
423
527
|
}
|
|
424
528
|
/**
|
|
425
|
-
* Entidad
|
|
529
|
+
* Entidad view - Vista optimizada para lectura (projections, denormalizaciones)
|
|
530
|
+
* Read-only, puede ser vista de BD o virtual
|
|
426
531
|
*/
|
|
427
|
-
interface
|
|
532
|
+
interface ViewEntityDefinition {
|
|
428
533
|
type: 'view';
|
|
429
|
-
|
|
534
|
+
/** Tabla o vista en BD (puede ser VIEW SQL) */
|
|
535
|
+
table: string;
|
|
536
|
+
/** Nombre para mostrar en UI */
|
|
537
|
+
label: string;
|
|
538
|
+
/** Campo para mostrar en selects/referencias */
|
|
430
539
|
labelField: string;
|
|
431
|
-
|
|
540
|
+
/** Definición de campos */
|
|
541
|
+
fields: Record<string, FieldDefinition>;
|
|
542
|
+
/** Entidad fuente de la que deriva */
|
|
543
|
+
sourceEntity?: string;
|
|
544
|
+
/** Autorización CASL */
|
|
545
|
+
casl?: EntityCaslConfig;
|
|
432
546
|
}
|
|
433
547
|
/**
|
|
434
|
-
* Entidad
|
|
548
|
+
* Entidad config - Configuración por módulo/tenant
|
|
549
|
+
* Similar a single pero con scope
|
|
435
550
|
*/
|
|
436
|
-
interface
|
|
437
|
-
type: '
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
551
|
+
interface ConfigEntityDefinition extends BaseEntityDefinition {
|
|
552
|
+
type: 'config';
|
|
553
|
+
/** Scope de la configuración */
|
|
554
|
+
scope?: 'global' | 'module' | 'tenant' | 'user';
|
|
555
|
+
/** Añadir updated_at */
|
|
556
|
+
timestamps?: boolean;
|
|
557
|
+
/** Añadir updated_by */
|
|
558
|
+
audit?: boolean;
|
|
441
559
|
}
|
|
442
560
|
/**
|
|
443
|
-
* Entidad
|
|
561
|
+
* Entidad temporal - Cache, sesiones, OTP codes
|
|
562
|
+
* Con TTL automático, sin auditoría
|
|
444
563
|
*/
|
|
445
|
-
interface
|
|
446
|
-
type: '
|
|
447
|
-
|
|
564
|
+
interface TempEntityDefinition extends BaseEntityDefinition {
|
|
565
|
+
type: 'temp';
|
|
566
|
+
/** Tiempo de vida en segundos */
|
|
567
|
+
ttl: number;
|
|
568
|
+
/** Campo para mostrar en listas (opcional) */
|
|
569
|
+
labelField?: string;
|
|
570
|
+
/** Índices para búsqueda rápida */
|
|
571
|
+
indexes?: EntityIndex[];
|
|
448
572
|
}
|
|
449
573
|
/**
|
|
450
|
-
*
|
|
574
|
+
* Union de todas las definiciones de entidad
|
|
451
575
|
*
|
|
452
576
|
* | Type | Persistencia | CRUD | Uso principal |
|
|
453
577
|
* |------------|--------------|-----------------|----------------------------------|
|
|
@@ -463,11 +587,7 @@ interface ActionEntity extends BaseEntity {
|
|
|
463
587
|
* | temp | No (TTL) | Read/Write | Cache, sesiones (otp_codes) |
|
|
464
588
|
* | action | No | Execute | Operaciones, workflows |
|
|
465
589
|
*/
|
|
466
|
-
type
|
|
467
|
-
/**
|
|
468
|
-
* Helper type para extraer el tipo de entidad
|
|
469
|
-
*/
|
|
470
|
-
type EntityType = ModuleEntity['type'];
|
|
590
|
+
type EntityDefinition = CollectionEntityDefinition | SingleEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition | ComputedEntityDefinition | ViewEntityDefinition | ReferenceEntityDefinition | ConfigEntityDefinition | EventEntityDefinition | TempEntityDefinition | ActionEntityDefinition;
|
|
471
591
|
/**
|
|
472
592
|
* Requisitos para activar un módulo
|
|
473
593
|
*/
|
|
@@ -500,6 +620,10 @@ interface ForbiddenErrorConstructor {
|
|
|
500
620
|
* Permite usar CASL en plugins sin importar @casl/ability directamente
|
|
501
621
|
*/
|
|
502
622
|
interface ModuleAbilities {
|
|
623
|
+
/** Crea una ability CASL para un usuario (acepta argumentos adicionales como permissions) */
|
|
624
|
+
defineAbilityFor: (user: any, ...args: any[]) => any;
|
|
625
|
+
/** Empaqueta reglas CASL para enviar al cliente (recibe ability, retorna reglas) */
|
|
626
|
+
packRules: (ability: any) => unknown[];
|
|
503
627
|
/** Wrapper para verificar permisos contra instancias */
|
|
504
628
|
subject: (type: string, object: Record<string, any>) => unknown;
|
|
505
629
|
/** Error de CASL para throwUnlessCan */
|
|
@@ -567,8 +691,8 @@ interface ModuleContext {
|
|
|
567
691
|
createRouter: () => Router;
|
|
568
692
|
middleware: ModuleMiddlewares;
|
|
569
693
|
registerMiddleware: (name: string, handler: RequestHandler) => void;
|
|
570
|
-
/** Configuración resuelta de la aplicación */
|
|
571
|
-
config:
|
|
694
|
+
/** Configuración resuelta de la aplicación (permite propiedades tipadas del backend) */
|
|
695
|
+
config: any;
|
|
572
696
|
errors: {
|
|
573
697
|
AppError: new (message: string, statusCode?: number) => Error;
|
|
574
698
|
NotFoundError: new (message?: string) => Error;
|
|
@@ -577,10 +701,8 @@ interface ModuleContext {
|
|
|
577
701
|
ConflictError: new (message?: string) => Error;
|
|
578
702
|
};
|
|
579
703
|
abilities: ModuleAbilities;
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
on: (event: string, listener: (...args: unknown[]) => void) => unknown;
|
|
583
|
-
};
|
|
704
|
+
/** Sistema de eventos (EventEmitter2 compatible, permite implementaciones tipadas) */
|
|
705
|
+
events: any;
|
|
584
706
|
mail: {
|
|
585
707
|
send: (options: {
|
|
586
708
|
to: string | string[];
|
|
@@ -632,11 +754,6 @@ interface ModuleManifest {
|
|
|
632
754
|
* Sus subjects se registran automáticamente.
|
|
633
755
|
*/
|
|
634
756
|
definitions?: EntityDefinition[];
|
|
635
|
-
/**
|
|
636
|
-
* @deprecated Usar `definitions` en su lugar.
|
|
637
|
-
* Entidades/tablas del módulo con config CRUD para UI.
|
|
638
|
-
*/
|
|
639
|
-
entities?: ModuleEntity[];
|
|
640
757
|
}
|
|
641
758
|
/**
|
|
642
759
|
* Categorías disponibles para plugins
|
|
@@ -664,4 +781,4 @@ interface PluginManifest {
|
|
|
664
781
|
modules: ModuleManifest[];
|
|
665
782
|
}
|
|
666
783
|
|
|
667
|
-
export { type AbilityLike, type
|
|
784
|
+
export { type AbilityLike, type ActionEntityDefinition, type AuthRequest, type BaseUser, type CaslAction, type CollectionEntityDefinition, type ComputedEntityDefinition, type ConfigEntityDefinition, type CoreServices, type DbType, type EntityCaslConfig, type EntityDefinition, type EntityIndex, type EventEntityDefinition, type ExternalEntityDefinition, type FieldCaslAccess, type FieldDbConfig, type FieldDefinition, type FieldMeta, type FieldOptions, type FieldRelation, type FieldValidation, type FieldValidationConfig, type ForbiddenErrorConstructor, type ForbiddenErrorInstance, type FormField, type FormFieldType, type GeneratedPermission, type InputType, type KnexAlterTableBuilder, type KnexCreateTableBuilder, type KnexTransaction, type ListType, type MigrationHelpers, type ModuleAbilities, type ModuleContext, type ModuleManifest, type ModuleMiddlewares, type ModuleRequirements, type NonPersistentEntityDefinition, type OwnershipCondition, type PaginatedResult, type PaginationParams, type PersistentEntityDefinition, type PluginAuthRequest, type PluginCategory, type PluginManifest, type ReferenceEntityDefinition, type RolePermission, type SingleEntityDefinition, type TempEntityDefinition, type UsersResolver, type ValidateSchemas, type ValidationSchema, type ViewEntityDefinition, type VirtualEntityDefinition, generateCaslPermissions, generateCaslSeed, generateMigration, generateModel, generateModelsFile, generateReadOnlyModel, generateReadOnlySchema, generateSchemasFile, generateZodSchema, getEntityName, getEntitySubject, hasTable, isPersistentEntity, isSingletonEntity };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
// src/generators.ts
|
|
2
|
+
function isPersistentEntity(entity) {
|
|
3
|
+
const withoutOwnTable = ["action", "external", "virtual", "computed", "single"];
|
|
4
|
+
return !withoutOwnTable.includes(entity.type ?? "collection");
|
|
5
|
+
}
|
|
6
|
+
function isSingletonEntity(entity) {
|
|
7
|
+
return entity.type === "single";
|
|
8
|
+
}
|
|
9
|
+
function hasTable(entity) {
|
|
10
|
+
return "table" in entity && typeof entity.table === "string";
|
|
11
|
+
}
|
|
2
12
|
function generateMigration(entity) {
|
|
3
|
-
const { table, fields
|
|
13
|
+
const { table, fields } = entity;
|
|
14
|
+
const timestamps = "timestamps" in entity ? entity.timestamps : false;
|
|
15
|
+
const audit = "audit" in entity ? entity.audit : false;
|
|
16
|
+
const indexes = "indexes" in entity ? entity.indexes : void 0;
|
|
4
17
|
const lines = [
|
|
5
18
|
`import type { ModuleContext, Knex } from '@gzl10/nexus-sdk'`,
|
|
6
19
|
``,
|
|
@@ -236,7 +249,9 @@ function dbTypeToZodType(dbType) {
|
|
|
236
249
|
}
|
|
237
250
|
}
|
|
238
251
|
function generateModel(entity) {
|
|
239
|
-
const { table, fields
|
|
252
|
+
const { table, fields } = entity;
|
|
253
|
+
const timestamps = "timestamps" in entity ? entity.timestamps : false;
|
|
254
|
+
const audit = "audit" in entity ? entity.audit : false;
|
|
240
255
|
const entityName = tableToEntityName(table);
|
|
241
256
|
const lines = [
|
|
242
257
|
`/**`,
|
|
@@ -282,19 +297,81 @@ function dbTypeToTsType(db) {
|
|
|
282
297
|
return "unknown";
|
|
283
298
|
}
|
|
284
299
|
}
|
|
300
|
+
function toPascalCase(str) {
|
|
301
|
+
return str.split("_").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
302
|
+
}
|
|
303
|
+
function toSingular(str) {
|
|
304
|
+
if (str.endsWith("ies")) return str.slice(0, -3) + "y";
|
|
305
|
+
if (str.endsWith("s")) return str.slice(0, -1);
|
|
306
|
+
return str;
|
|
307
|
+
}
|
|
285
308
|
function tableToEntityName(table) {
|
|
286
309
|
const withoutPrefix = table.replace(/^[a-z]{2,4}_/, "");
|
|
287
|
-
|
|
288
|
-
return singular.charAt(0).toUpperCase() + singular.slice(1);
|
|
310
|
+
return toPascalCase(toSingular(withoutPrefix));
|
|
289
311
|
}
|
|
290
312
|
function tableToSubject(table) {
|
|
291
313
|
const match = table.match(/^([a-z]{2,4})_(.+)$/);
|
|
292
314
|
if (!match) return tableToEntityName(table);
|
|
293
315
|
const [, prefix, rest] = match;
|
|
294
316
|
const prefixPascal = prefix.charAt(0).toUpperCase() + prefix.slice(1);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
317
|
+
return prefixPascal + toPascalCase(toSingular(rest));
|
|
318
|
+
}
|
|
319
|
+
function labelToEntityName(label) {
|
|
320
|
+
const singular = label.endsWith("ies") ? label.slice(0, -3) + "y" : label.endsWith("s") ? label.slice(0, -1) : label;
|
|
321
|
+
return singular.charAt(0).toUpperCase() + singular.slice(1).replace(/\s+/g, "");
|
|
322
|
+
}
|
|
323
|
+
function generateReadOnlySchema(entity) {
|
|
324
|
+
const { label, fields } = entity;
|
|
325
|
+
const entityName = labelToEntityName(label);
|
|
326
|
+
const lines = [
|
|
327
|
+
`import { z } from 'zod'`,
|
|
328
|
+
``,
|
|
329
|
+
`// === OUTPUT SCHEMA ===`,
|
|
330
|
+
`export const ${entityName.toLowerCase()}Schema = z.object({`
|
|
331
|
+
];
|
|
332
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
333
|
+
const zodType = dbTypeToZodType(field.db.type);
|
|
334
|
+
const nullable = field.db.nullable ? `.nullable()` : "";
|
|
335
|
+
lines.push(` ${name}: ${zodType}${nullable},`);
|
|
336
|
+
}
|
|
337
|
+
lines.push(`})`);
|
|
338
|
+
lines.push(``);
|
|
339
|
+
lines.push(`// === QUERY ===`);
|
|
340
|
+
lines.push(`export const ${entityName.toLowerCase()}QuerySchema = z.object({`);
|
|
341
|
+
lines.push(` page: z.coerce.number().int().min(1).default(1),`);
|
|
342
|
+
lines.push(` limit: z.coerce.number().int().min(1).max(100).default(20),`);
|
|
343
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
344
|
+
if (field.meta?.searchable) {
|
|
345
|
+
const zodType = dbTypeToZodType(field.db.type);
|
|
346
|
+
lines.push(` ${name}: ${zodType}.optional(),`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
lines.push(`})`);
|
|
350
|
+
lines.push(``);
|
|
351
|
+
lines.push(`// === INFERRED TYPES ===`);
|
|
352
|
+
lines.push(`export type ${entityName} = z.infer<typeof ${entityName.toLowerCase()}Schema>`);
|
|
353
|
+
lines.push(`export type ${entityName}Query = z.infer<typeof ${entityName.toLowerCase()}QuerySchema>`);
|
|
354
|
+
lines.push(``);
|
|
355
|
+
return lines.join("\n");
|
|
356
|
+
}
|
|
357
|
+
function generateReadOnlyModel(entity) {
|
|
358
|
+
const { label, fields } = entity;
|
|
359
|
+
const entityName = labelToEntityName(label);
|
|
360
|
+
const lines = [
|
|
361
|
+
`/**`,
|
|
362
|
+
` * ${label}`,
|
|
363
|
+
` * Generated from EntityDefinition (${entity.type})`,
|
|
364
|
+
` */`,
|
|
365
|
+
`export interface ${entityName} {`
|
|
366
|
+
];
|
|
367
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
368
|
+
const tsType = dbTypeToTsType(field.db);
|
|
369
|
+
const optional = field.db.nullable ? "?" : "";
|
|
370
|
+
lines.push(` ${name}${optional}: ${tsType}`);
|
|
371
|
+
}
|
|
372
|
+
lines.push(`}`);
|
|
373
|
+
lines.push(``);
|
|
374
|
+
return lines.join("\n");
|
|
298
375
|
}
|
|
299
376
|
function getFieldsForRole(entity, role, action) {
|
|
300
377
|
const { fields, casl } = entity;
|
|
@@ -365,7 +442,9 @@ function generateCaslPermissions(entity) {
|
|
|
365
442
|
function generateCaslSeed(entities) {
|
|
366
443
|
const allPermissions = [];
|
|
367
444
|
for (const entity of entities) {
|
|
368
|
-
|
|
445
|
+
if (isPersistentEntity(entity)) {
|
|
446
|
+
allPermissions.push(...generateCaslPermissions(entity));
|
|
447
|
+
}
|
|
369
448
|
}
|
|
370
449
|
if (allPermissions.length === 0) {
|
|
371
450
|
return "// No CASL permissions defined in entities\n";
|
|
@@ -430,11 +509,158 @@ function generateCaslSeed(entities) {
|
|
|
430
509
|
function getEntitySubject(entity) {
|
|
431
510
|
return entity.casl?.subject ?? tableToSubject(entity.table);
|
|
432
511
|
}
|
|
512
|
+
function getEntityName(entity) {
|
|
513
|
+
if ("table" in entity) {
|
|
514
|
+
const withoutPrefix = entity.table.replace(/^[a-z]{2,4}_/, "");
|
|
515
|
+
return toPascalCase(toSingular(withoutPrefix));
|
|
516
|
+
} else if ("key" in entity) {
|
|
517
|
+
return toPascalCase(entity.key);
|
|
518
|
+
} else {
|
|
519
|
+
return toSingular(entity.label).replace(/\s+/g, "");
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function dbTypeToZodSimple(type, nullable) {
|
|
523
|
+
let zod = "";
|
|
524
|
+
switch (type) {
|
|
525
|
+
case "string":
|
|
526
|
+
case "text":
|
|
527
|
+
case "uuid":
|
|
528
|
+
zod = "z.string()";
|
|
529
|
+
break;
|
|
530
|
+
case "integer":
|
|
531
|
+
zod = "z.number().int()";
|
|
532
|
+
break;
|
|
533
|
+
case "decimal":
|
|
534
|
+
zod = "z.number()";
|
|
535
|
+
break;
|
|
536
|
+
case "boolean":
|
|
537
|
+
zod = "z.boolean()";
|
|
538
|
+
break;
|
|
539
|
+
case "date":
|
|
540
|
+
case "datetime":
|
|
541
|
+
zod = "z.string().datetime()";
|
|
542
|
+
break;
|
|
543
|
+
case "json":
|
|
544
|
+
zod = "z.record(z.unknown())";
|
|
545
|
+
break;
|
|
546
|
+
default:
|
|
547
|
+
zod = "z.unknown()";
|
|
548
|
+
}
|
|
549
|
+
return nullable ? `${zod}.nullable()` : zod;
|
|
550
|
+
}
|
|
551
|
+
function dbTypeToTsSimple(type) {
|
|
552
|
+
switch (type) {
|
|
553
|
+
case "string":
|
|
554
|
+
case "text":
|
|
555
|
+
case "uuid":
|
|
556
|
+
return "string";
|
|
557
|
+
case "integer":
|
|
558
|
+
case "decimal":
|
|
559
|
+
return "number";
|
|
560
|
+
case "boolean":
|
|
561
|
+
return "boolean";
|
|
562
|
+
case "date":
|
|
563
|
+
case "datetime":
|
|
564
|
+
return "Date";
|
|
565
|
+
case "json":
|
|
566
|
+
return "Record<string, unknown>";
|
|
567
|
+
default:
|
|
568
|
+
return "unknown";
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function generateSchemasFile(definitions) {
|
|
572
|
+
const lines = [
|
|
573
|
+
"/**",
|
|
574
|
+
" * AUTO-GENERATED - Do not edit manually",
|
|
575
|
+
" * Generated from EntityDefinition via @gzl10/nexus-sdk generators",
|
|
576
|
+
" */",
|
|
577
|
+
"",
|
|
578
|
+
"import { z } from 'zod'",
|
|
579
|
+
""
|
|
580
|
+
];
|
|
581
|
+
for (const entity of definitions) {
|
|
582
|
+
const name = getEntityName(entity);
|
|
583
|
+
lines.push(`// ============================================================================`);
|
|
584
|
+
lines.push(`// ${name.toUpperCase()} (${entity.type ?? "collection"})`);
|
|
585
|
+
lines.push(`// ============================================================================`);
|
|
586
|
+
lines.push("");
|
|
587
|
+
if (isPersistentEntity(entity)) {
|
|
588
|
+
const schema = generateZodSchema(entity);
|
|
589
|
+
const schemaLines = schema.split("\n").filter(
|
|
590
|
+
(l) => !l.startsWith("import") && l.trim() !== ""
|
|
591
|
+
);
|
|
592
|
+
lines.push(...schemaLines);
|
|
593
|
+
} else if (entity.type === "single") {
|
|
594
|
+
lines.push(`export const ${name.toLowerCase()}Schema = z.object({`);
|
|
595
|
+
for (const [fieldName, field] of Object.entries(entity.fields)) {
|
|
596
|
+
const zodType = dbTypeToZodSimple(field.db.type, field.db.nullable);
|
|
597
|
+
lines.push(` ${fieldName}: ${zodType},`);
|
|
598
|
+
}
|
|
599
|
+
lines.push("})");
|
|
600
|
+
lines.push("");
|
|
601
|
+
lines.push(`export type ${name} = z.infer<typeof ${name.toLowerCase()}Schema>`);
|
|
602
|
+
} else {
|
|
603
|
+
const schema = generateReadOnlySchema(entity);
|
|
604
|
+
const schemaLines = schema.split("\n").filter(
|
|
605
|
+
(l) => !l.startsWith("import") && l.trim() !== ""
|
|
606
|
+
);
|
|
607
|
+
lines.push(...schemaLines);
|
|
608
|
+
}
|
|
609
|
+
lines.push("");
|
|
610
|
+
}
|
|
611
|
+
return lines.join("\n");
|
|
612
|
+
}
|
|
613
|
+
function generateModelsFile(definitions) {
|
|
614
|
+
const lines = [
|
|
615
|
+
"/**",
|
|
616
|
+
" * AUTO-GENERATED - Do not edit manually",
|
|
617
|
+
" * Generated from EntityDefinition via @gzl10/nexus-sdk generators",
|
|
618
|
+
" */",
|
|
619
|
+
""
|
|
620
|
+
];
|
|
621
|
+
for (const entity of definitions) {
|
|
622
|
+
const name = getEntityName(entity);
|
|
623
|
+
lines.push(`// ============================================================================`);
|
|
624
|
+
lines.push(`// ${name.toUpperCase()} (${entity.type ?? "collection"})`);
|
|
625
|
+
lines.push(`// ============================================================================`);
|
|
626
|
+
lines.push("");
|
|
627
|
+
if (isPersistentEntity(entity)) {
|
|
628
|
+
const model = generateModel(entity);
|
|
629
|
+
const modelLines = model.split("\n");
|
|
630
|
+
lines.push(...modelLines);
|
|
631
|
+
} else if (entity.type === "single") {
|
|
632
|
+
lines.push(`/**`);
|
|
633
|
+
lines.push(` * ${entity.label}`);
|
|
634
|
+
lines.push(` * Generated from EntityDefinition (single)`);
|
|
635
|
+
lines.push(` */`);
|
|
636
|
+
lines.push(`export interface ${name} {`);
|
|
637
|
+
for (const [fieldName, field] of Object.entries(entity.fields)) {
|
|
638
|
+
const tsType = dbTypeToTsSimple(field.db.type);
|
|
639
|
+
const optional = field.db.nullable ? "?" : "";
|
|
640
|
+
lines.push(` ${fieldName}${optional}: ${tsType}`);
|
|
641
|
+
}
|
|
642
|
+
lines.push("}");
|
|
643
|
+
} else {
|
|
644
|
+
const model = generateReadOnlyModel(entity);
|
|
645
|
+
lines.push(model);
|
|
646
|
+
}
|
|
647
|
+
lines.push("");
|
|
648
|
+
}
|
|
649
|
+
return lines.join("\n");
|
|
650
|
+
}
|
|
433
651
|
export {
|
|
434
652
|
generateCaslPermissions,
|
|
435
653
|
generateCaslSeed,
|
|
436
654
|
generateMigration,
|
|
437
655
|
generateModel,
|
|
656
|
+
generateModelsFile,
|
|
657
|
+
generateReadOnlyModel,
|
|
658
|
+
generateReadOnlySchema,
|
|
659
|
+
generateSchemasFile,
|
|
438
660
|
generateZodSchema,
|
|
439
|
-
|
|
661
|
+
getEntityName,
|
|
662
|
+
getEntitySubject,
|
|
663
|
+
hasTable,
|
|
664
|
+
isPersistentEntity,
|
|
665
|
+
isSingletonEntity
|
|
440
666
|
};
|