@gzl10/nexus-sdk 0.3.0 → 0.4.1
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 +82 -30
- package/dist/index.js +362 -8
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -11,11 +11,29 @@ import { Logger } from 'pino';
|
|
|
11
11
|
* que pueden ser escritos a archivos o usados en runtime.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/** Directorio estándar para código generado */
|
|
15
|
+
declare const GENERATED_DIR = "__generated__";
|
|
14
16
|
/**
|
|
15
|
-
* Entidades que persisten en BD local
|
|
16
|
-
* Excluye: action, external, virtual, computed
|
|
17
|
+
* Entidades que persisten en BD local con tabla propia
|
|
18
|
+
* Excluye: action, external, virtual, computed, single (usa tabla compartida)
|
|
17
19
|
*/
|
|
18
|
-
type PersistentEntityDefinition = CollectionEntityDefinition |
|
|
20
|
+
type PersistentEntityDefinition = CollectionEntityDefinition | ReferenceEntityDefinition | EventEntityDefinition | ConfigEntityDefinition | TempEntityDefinition | ViewEntityDefinition;
|
|
21
|
+
/**
|
|
22
|
+
* Entidades sin persistencia local (read-only o sin BD)
|
|
23
|
+
*/
|
|
24
|
+
type NonPersistentEntityDefinition = ActionEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition | ComputedEntityDefinition;
|
|
25
|
+
/**
|
|
26
|
+
* Type guard para verificar si una entidad persiste en BD local con tabla propia
|
|
27
|
+
*/
|
|
28
|
+
declare function isPersistentEntity(entity: EntityDefinition): entity is PersistentEntityDefinition;
|
|
29
|
+
/**
|
|
30
|
+
* Type guard para verificar si es un singleton (usa tabla compartida sys_settings)
|
|
31
|
+
*/
|
|
32
|
+
declare function isSingletonEntity(entity: EntityDefinition): entity is SingleEntityDefinition;
|
|
33
|
+
/**
|
|
34
|
+
* Type guard para verificar si una entidad tiene tabla propia (para migraciones)
|
|
35
|
+
*/
|
|
36
|
+
declare function hasTable(entity: EntityDefinition): entity is PersistentEntityDefinition;
|
|
19
37
|
/**
|
|
20
38
|
* Genera código de migración Knex desde EntityDefinition
|
|
21
39
|
*
|
|
@@ -51,6 +69,15 @@ declare function generateZodSchema(entity: PersistentEntityDefinition): string;
|
|
|
51
69
|
* // }
|
|
52
70
|
*/
|
|
53
71
|
declare function generateModel(entity: PersistentEntityDefinition): string;
|
|
72
|
+
/**
|
|
73
|
+
* Genera schema Zod para entidades read-only
|
|
74
|
+
* Solo genera el schema de output, no create/update
|
|
75
|
+
*/
|
|
76
|
+
declare function generateReadOnlySchema(entity: ComputedEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition): string;
|
|
77
|
+
/**
|
|
78
|
+
* Genera interface TypeScript para entidades read-only
|
|
79
|
+
*/
|
|
80
|
+
declare function generateReadOnlyModel(entity: ComputedEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition): string;
|
|
54
81
|
/**
|
|
55
82
|
* Estructura de permiso generado para insertar en BD
|
|
56
83
|
*/
|
|
@@ -82,6 +109,38 @@ declare function generateCaslSeed(entities: EntityDefinition[]): string;
|
|
|
82
109
|
* Obtiene el subject CASL de una entidad
|
|
83
110
|
*/
|
|
84
111
|
declare function getEntitySubject(entity: PersistentEntityDefinition): string;
|
|
112
|
+
/**
|
|
113
|
+
* Obtiene el nombre de una entidad en PascalCase singular
|
|
114
|
+
* 'cms_posts' → 'Post', 'rol_role_permissions' → 'RolePermission'
|
|
115
|
+
*/
|
|
116
|
+
declare function getEntityName(entity: EntityDefinition): string;
|
|
117
|
+
/**
|
|
118
|
+
* Genera archivo completo de schemas Zod para múltiples entidades
|
|
119
|
+
* Consolida todas las entidades de un módulo en un solo archivo
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* const code = generateSchemasFile([userEntity, roleEntity])
|
|
123
|
+
* writeFileSync('users.schemas.ts', code)
|
|
124
|
+
*/
|
|
125
|
+
declare function generateSchemasFile(definitions: EntityDefinition[]): string;
|
|
126
|
+
/**
|
|
127
|
+
* Genera archivo completo de modelos TypeScript para múltiples entidades
|
|
128
|
+
* Consolida todas las entidades de un módulo en un solo archivo
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* const code = generateModelsFile([userEntity, roleEntity])
|
|
132
|
+
* writeFileSync('users.models.ts', code)
|
|
133
|
+
*/
|
|
134
|
+
declare function generateModelsFile(definitions: EntityDefinition[]): string;
|
|
135
|
+
/**
|
|
136
|
+
* Genera archivo completo de migración Knex para múltiples entidades
|
|
137
|
+
* Consolida todas las entidades de un módulo en un solo archivo de migración
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* const code = generateMigrationsFile([userEntity, roleEntity])
|
|
141
|
+
* writeFileSync('users.migrate.ts', code)
|
|
142
|
+
*/
|
|
143
|
+
declare function generateMigrationsFile(definitions: EntityDefinition[]): string;
|
|
85
144
|
|
|
86
145
|
/**
|
|
87
146
|
* @gzl10/nexus-sdk
|
|
@@ -370,14 +429,24 @@ interface CollectionEntityDefinition extends BaseEntityDefinition {
|
|
|
370
429
|
indexes?: EntityIndex[];
|
|
371
430
|
}
|
|
372
431
|
/**
|
|
373
|
-
* Entidad singleton -
|
|
432
|
+
* Entidad singleton - Un solo registro en tabla compartida sys_settings
|
|
433
|
+
* Usa key-value: key identifica el registro, value es JSON con la estructura de fields
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* // site_config singleton
|
|
437
|
+
* { type: 'single', key: 'site_config', label: 'Site Config', fields: { siteName: {...}, logo: {...} } }
|
|
438
|
+
* // Se guarda en sys_settings como: { key: 'site_config', value: { siteName: 'Mi App', logo: '...' } }
|
|
374
439
|
*/
|
|
375
|
-
interface SingleEntityDefinition
|
|
440
|
+
interface SingleEntityDefinition {
|
|
376
441
|
type: 'single';
|
|
377
|
-
/**
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
|
|
442
|
+
/** Clave única en tabla sys_settings (ej: 'site_config', 'smtp_settings') */
|
|
443
|
+
key: string;
|
|
444
|
+
/** Nombre para mostrar en UI */
|
|
445
|
+
label: string;
|
|
446
|
+
/** Definición de campos (estructura del JSON value) */
|
|
447
|
+
fields: Record<string, FieldDefinition>;
|
|
448
|
+
/** Autorización CASL */
|
|
449
|
+
casl?: EntityCaslConfig;
|
|
381
450
|
}
|
|
382
451
|
/**
|
|
383
452
|
* Entidad de referencia - Catálogos con CRUD admin (countries, currencies)
|
|
@@ -576,21 +645,6 @@ interface ModuleAbilities {
|
|
|
576
645
|
* Usa BaseUser y AbilityLike para tipado básico sin depender del backend
|
|
577
646
|
*/
|
|
578
647
|
type PluginAuthRequest = AuthRequest<BaseUser, AbilityLike>;
|
|
579
|
-
/**
|
|
580
|
-
* Resolver de usuarios para plugins
|
|
581
|
-
* Permite acceder a usuarios sin conocer la implementación interna
|
|
582
|
-
*/
|
|
583
|
-
interface UsersResolver {
|
|
584
|
-
findById: (id: string) => Promise<BaseUser | null>;
|
|
585
|
-
findByIds: (ids: string[]) => Promise<BaseUser[]>;
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* Servicios core inyectados por el backend
|
|
589
|
-
* Los plugins acceden a estos servicios via ctx.services
|
|
590
|
-
*/
|
|
591
|
-
interface CoreServices {
|
|
592
|
-
users?: UsersResolver;
|
|
593
|
-
}
|
|
594
648
|
/**
|
|
595
649
|
* Helpers para migraciones de base de datos
|
|
596
650
|
*/
|
|
@@ -655,8 +709,8 @@ interface ModuleContext {
|
|
|
655
709
|
data?: Record<string, unknown>;
|
|
656
710
|
}) => Promise<unknown>;
|
|
657
711
|
};
|
|
658
|
-
/** Servicios de módulos
|
|
659
|
-
services:
|
|
712
|
+
/** Servicios de módulos (inyectados por backend) */
|
|
713
|
+
services: Record<string, unknown>;
|
|
660
714
|
}
|
|
661
715
|
/**
|
|
662
716
|
* Manifest de un módulo Nexus
|
|
@@ -664,8 +718,6 @@ interface ModuleContext {
|
|
|
664
718
|
interface ModuleManifest {
|
|
665
719
|
/** Identificador único del módulo (ej: 'users', 'posts') */
|
|
666
720
|
name: string;
|
|
667
|
-
/** Código único del módulo (ej: 'USR', 'PST'). Opcional si pertenece a un plugin */
|
|
668
|
-
code?: string;
|
|
669
721
|
/** Nombre para mostrar en UI. Opcional si pertenece a un plugin */
|
|
670
722
|
label?: string;
|
|
671
723
|
/** Icono del módulo (Iconify MDI: 'mdi:account-group') */
|
|
@@ -678,7 +730,7 @@ interface ModuleManifest {
|
|
|
678
730
|
dependencies?: string[];
|
|
679
731
|
/** Requisitos para activar el módulo */
|
|
680
732
|
required?: ModuleRequirements;
|
|
681
|
-
/** Función de migración de base de datos */
|
|
733
|
+
/** Función de migración de base de datos. Normalmente se genera desde definitions (EntityDefinition) */
|
|
682
734
|
migrate?: (ctx: ModuleContext) => Promise<void>;
|
|
683
735
|
/** Función de seed de datos iniciales */
|
|
684
736
|
seed?: (ctx: ModuleContext) => Promise<void>;
|
|
@@ -723,4 +775,4 @@ interface PluginManifest {
|
|
|
723
775
|
modules: ModuleManifest[];
|
|
724
776
|
}
|
|
725
777
|
|
|
726
|
-
export { type AbilityLike, type ActionEntityDefinition, type AuthRequest, type BaseUser, type CaslAction, type CollectionEntityDefinition, type ComputedEntityDefinition, type ConfigEntityDefinition, type
|
|
778
|
+
export { type AbilityLike, type ActionEntityDefinition, type AuthRequest, type BaseUser, type CaslAction, type CollectionEntityDefinition, type ComputedEntityDefinition, type ConfigEntityDefinition, 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, GENERATED_DIR, 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 ValidateSchemas, type ValidationSchema, type ViewEntityDefinition, type VirtualEntityDefinition, generateCaslPermissions, generateCaslSeed, generateMigration, generateMigrationsFile, generateModel, generateModelsFile, generateReadOnlyModel, generateReadOnlySchema, generateSchemasFile, generateZodSchema, getEntityName, getEntitySubject, hasTable, isPersistentEntity, isSingletonEntity };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
// src/generators.ts
|
|
2
|
+
var GENERATED_DIR = "__generated__";
|
|
2
3
|
function isPersistentEntity(entity) {
|
|
3
|
-
const
|
|
4
|
-
return !
|
|
4
|
+
const withoutOwnTable = ["action", "external", "virtual", "computed", "single"];
|
|
5
|
+
return !withoutOwnTable.includes(entity.type ?? "collection");
|
|
6
|
+
}
|
|
7
|
+
function isSingletonEntity(entity) {
|
|
8
|
+
return entity.type === "single";
|
|
9
|
+
}
|
|
10
|
+
function hasTable(entity) {
|
|
11
|
+
return "table" in entity && typeof entity.table === "string";
|
|
5
12
|
}
|
|
6
13
|
function generateMigration(entity) {
|
|
7
14
|
const { table, fields } = entity;
|
|
@@ -291,19 +298,81 @@ function dbTypeToTsType(db) {
|
|
|
291
298
|
return "unknown";
|
|
292
299
|
}
|
|
293
300
|
}
|
|
301
|
+
function toPascalCase(str) {
|
|
302
|
+
return str.split("_").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
303
|
+
}
|
|
304
|
+
function toSingular(str) {
|
|
305
|
+
if (str.endsWith("ies")) return str.slice(0, -3) + "y";
|
|
306
|
+
if (str.endsWith("s")) return str.slice(0, -1);
|
|
307
|
+
return str;
|
|
308
|
+
}
|
|
294
309
|
function tableToEntityName(table) {
|
|
295
310
|
const withoutPrefix = table.replace(/^[a-z]{2,4}_/, "");
|
|
296
|
-
|
|
297
|
-
return singular.charAt(0).toUpperCase() + singular.slice(1);
|
|
311
|
+
return toPascalCase(toSingular(withoutPrefix));
|
|
298
312
|
}
|
|
299
313
|
function tableToSubject(table) {
|
|
300
314
|
const match = table.match(/^([a-z]{2,4})_(.+)$/);
|
|
301
315
|
if (!match) return tableToEntityName(table);
|
|
302
316
|
const [, prefix, rest] = match;
|
|
303
317
|
const prefixPascal = prefix.charAt(0).toUpperCase() + prefix.slice(1);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
318
|
+
return prefixPascal + toPascalCase(toSingular(rest));
|
|
319
|
+
}
|
|
320
|
+
function labelToEntityName(label) {
|
|
321
|
+
const singular = label.endsWith("ies") ? label.slice(0, -3) + "y" : label.endsWith("s") ? label.slice(0, -1) : label;
|
|
322
|
+
return singular.charAt(0).toUpperCase() + singular.slice(1).replace(/\s+/g, "");
|
|
323
|
+
}
|
|
324
|
+
function generateReadOnlySchema(entity) {
|
|
325
|
+
const { label, fields } = entity;
|
|
326
|
+
const entityName = labelToEntityName(label);
|
|
327
|
+
const lines = [
|
|
328
|
+
`import { z } from 'zod'`,
|
|
329
|
+
``,
|
|
330
|
+
`// === OUTPUT SCHEMA ===`,
|
|
331
|
+
`export const ${entityName.toLowerCase()}Schema = z.object({`
|
|
332
|
+
];
|
|
333
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
334
|
+
const zodType = dbTypeToZodType(field.db.type);
|
|
335
|
+
const nullable = field.db.nullable ? `.nullable()` : "";
|
|
336
|
+
lines.push(` ${name}: ${zodType}${nullable},`);
|
|
337
|
+
}
|
|
338
|
+
lines.push(`})`);
|
|
339
|
+
lines.push(``);
|
|
340
|
+
lines.push(`// === QUERY ===`);
|
|
341
|
+
lines.push(`export const ${entityName.toLowerCase()}QuerySchema = z.object({`);
|
|
342
|
+
lines.push(` page: z.coerce.number().int().min(1).default(1),`);
|
|
343
|
+
lines.push(` limit: z.coerce.number().int().min(1).max(100).default(20),`);
|
|
344
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
345
|
+
if (field.meta?.searchable) {
|
|
346
|
+
const zodType = dbTypeToZodType(field.db.type);
|
|
347
|
+
lines.push(` ${name}: ${zodType}.optional(),`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
lines.push(`})`);
|
|
351
|
+
lines.push(``);
|
|
352
|
+
lines.push(`// === INFERRED TYPES ===`);
|
|
353
|
+
lines.push(`export type ${entityName} = z.infer<typeof ${entityName.toLowerCase()}Schema>`);
|
|
354
|
+
lines.push(`export type ${entityName}Query = z.infer<typeof ${entityName.toLowerCase()}QuerySchema>`);
|
|
355
|
+
lines.push(``);
|
|
356
|
+
return lines.join("\n");
|
|
357
|
+
}
|
|
358
|
+
function generateReadOnlyModel(entity) {
|
|
359
|
+
const { label, fields } = entity;
|
|
360
|
+
const entityName = labelToEntityName(label);
|
|
361
|
+
const lines = [
|
|
362
|
+
`/**`,
|
|
363
|
+
` * ${label}`,
|
|
364
|
+
` * Generated from EntityDefinition (${entity.type})`,
|
|
365
|
+
` */`,
|
|
366
|
+
`export interface ${entityName} {`
|
|
367
|
+
];
|
|
368
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
369
|
+
const tsType = dbTypeToTsType(field.db);
|
|
370
|
+
const optional = field.db.nullable ? "?" : "";
|
|
371
|
+
lines.push(` ${name}${optional}: ${tsType}`);
|
|
372
|
+
}
|
|
373
|
+
lines.push(`}`);
|
|
374
|
+
lines.push(``);
|
|
375
|
+
return lines.join("\n");
|
|
307
376
|
}
|
|
308
377
|
function getFieldsForRole(entity, role, action) {
|
|
309
378
|
const { fields, casl } = entity;
|
|
@@ -441,11 +510,296 @@ function generateCaslSeed(entities) {
|
|
|
441
510
|
function getEntitySubject(entity) {
|
|
442
511
|
return entity.casl?.subject ?? tableToSubject(entity.table);
|
|
443
512
|
}
|
|
513
|
+
function getEntityName(entity) {
|
|
514
|
+
if ("table" in entity) {
|
|
515
|
+
const withoutPrefix = entity.table.replace(/^[a-z]{2,4}_/, "");
|
|
516
|
+
return toPascalCase(toSingular(withoutPrefix));
|
|
517
|
+
} else if ("key" in entity) {
|
|
518
|
+
return toPascalCase(entity.key);
|
|
519
|
+
} else {
|
|
520
|
+
return toSingular(entity.label).replace(/\s+/g, "");
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function dbTypeToZodSimple(type, nullable) {
|
|
524
|
+
let zod = "";
|
|
525
|
+
switch (type) {
|
|
526
|
+
case "string":
|
|
527
|
+
case "text":
|
|
528
|
+
case "uuid":
|
|
529
|
+
zod = "z.string()";
|
|
530
|
+
break;
|
|
531
|
+
case "integer":
|
|
532
|
+
zod = "z.number().int()";
|
|
533
|
+
break;
|
|
534
|
+
case "decimal":
|
|
535
|
+
zod = "z.number()";
|
|
536
|
+
break;
|
|
537
|
+
case "boolean":
|
|
538
|
+
zod = "z.boolean()";
|
|
539
|
+
break;
|
|
540
|
+
case "date":
|
|
541
|
+
case "datetime":
|
|
542
|
+
zod = "z.string().datetime()";
|
|
543
|
+
break;
|
|
544
|
+
case "json":
|
|
545
|
+
zod = "z.record(z.unknown())";
|
|
546
|
+
break;
|
|
547
|
+
default:
|
|
548
|
+
zod = "z.unknown()";
|
|
549
|
+
}
|
|
550
|
+
return nullable ? `${zod}.nullable()` : zod;
|
|
551
|
+
}
|
|
552
|
+
function dbTypeToTsSimple(type) {
|
|
553
|
+
switch (type) {
|
|
554
|
+
case "string":
|
|
555
|
+
case "text":
|
|
556
|
+
case "uuid":
|
|
557
|
+
return "string";
|
|
558
|
+
case "integer":
|
|
559
|
+
case "decimal":
|
|
560
|
+
return "number";
|
|
561
|
+
case "boolean":
|
|
562
|
+
return "boolean";
|
|
563
|
+
case "date":
|
|
564
|
+
case "datetime":
|
|
565
|
+
return "Date";
|
|
566
|
+
case "json":
|
|
567
|
+
return "Record<string, unknown>";
|
|
568
|
+
default:
|
|
569
|
+
return "unknown";
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function generateSchemasFile(definitions) {
|
|
573
|
+
const lines = [
|
|
574
|
+
"/**",
|
|
575
|
+
" * AUTO-GENERATED - Do not edit manually",
|
|
576
|
+
" * Generated from EntityDefinition via @gzl10/nexus-sdk generators",
|
|
577
|
+
" */",
|
|
578
|
+
"",
|
|
579
|
+
"import { z } from 'zod'",
|
|
580
|
+
""
|
|
581
|
+
];
|
|
582
|
+
for (const entity of definitions) {
|
|
583
|
+
const name = getEntityName(entity);
|
|
584
|
+
lines.push(`// ============================================================================`);
|
|
585
|
+
lines.push(`// ${name.toUpperCase()} (${entity.type ?? "collection"})`);
|
|
586
|
+
lines.push(`// ============================================================================`);
|
|
587
|
+
lines.push("");
|
|
588
|
+
if (isPersistentEntity(entity)) {
|
|
589
|
+
const schema = generateZodSchema(entity);
|
|
590
|
+
const schemaLines = schema.split("\n").filter(
|
|
591
|
+
(l) => !l.startsWith("import") && l.trim() !== ""
|
|
592
|
+
);
|
|
593
|
+
lines.push(...schemaLines);
|
|
594
|
+
} else if (entity.type === "single") {
|
|
595
|
+
lines.push(`export const ${name.toLowerCase()}Schema = z.object({`);
|
|
596
|
+
for (const [fieldName, field] of Object.entries(entity.fields)) {
|
|
597
|
+
const zodType = dbTypeToZodSimple(field.db.type, field.db.nullable);
|
|
598
|
+
lines.push(` ${fieldName}: ${zodType},`);
|
|
599
|
+
}
|
|
600
|
+
lines.push("})");
|
|
601
|
+
lines.push("");
|
|
602
|
+
lines.push(`export type ${name} = z.infer<typeof ${name.toLowerCase()}Schema>`);
|
|
603
|
+
} else {
|
|
604
|
+
const schema = generateReadOnlySchema(entity);
|
|
605
|
+
const schemaLines = schema.split("\n").filter(
|
|
606
|
+
(l) => !l.startsWith("import") && l.trim() !== ""
|
|
607
|
+
);
|
|
608
|
+
lines.push(...schemaLines);
|
|
609
|
+
}
|
|
610
|
+
lines.push("");
|
|
611
|
+
}
|
|
612
|
+
return lines.join("\n");
|
|
613
|
+
}
|
|
614
|
+
function generateModelsFile(definitions) {
|
|
615
|
+
const lines = [
|
|
616
|
+
"/**",
|
|
617
|
+
" * AUTO-GENERATED - Do not edit manually",
|
|
618
|
+
" * Generated from EntityDefinition via @gzl10/nexus-sdk generators",
|
|
619
|
+
" */",
|
|
620
|
+
""
|
|
621
|
+
];
|
|
622
|
+
for (const entity of definitions) {
|
|
623
|
+
const name = getEntityName(entity);
|
|
624
|
+
lines.push(`// ============================================================================`);
|
|
625
|
+
lines.push(`// ${name.toUpperCase()} (${entity.type ?? "collection"})`);
|
|
626
|
+
lines.push(`// ============================================================================`);
|
|
627
|
+
lines.push("");
|
|
628
|
+
if (isPersistentEntity(entity)) {
|
|
629
|
+
const model = generateModel(entity);
|
|
630
|
+
const modelLines = model.split("\n");
|
|
631
|
+
lines.push(...modelLines);
|
|
632
|
+
} else if (entity.type === "single") {
|
|
633
|
+
lines.push(`/**`);
|
|
634
|
+
lines.push(` * ${entity.label}`);
|
|
635
|
+
lines.push(` * Generated from EntityDefinition (single)`);
|
|
636
|
+
lines.push(` */`);
|
|
637
|
+
lines.push(`export interface ${name} {`);
|
|
638
|
+
for (const [fieldName, field] of Object.entries(entity.fields)) {
|
|
639
|
+
const tsType = dbTypeToTsSimple(field.db.type);
|
|
640
|
+
const optional = field.db.nullable ? "?" : "";
|
|
641
|
+
lines.push(` ${fieldName}${optional}: ${tsType}`);
|
|
642
|
+
}
|
|
643
|
+
lines.push("}");
|
|
644
|
+
} else {
|
|
645
|
+
const model = generateReadOnlyModel(entity);
|
|
646
|
+
lines.push(model);
|
|
647
|
+
}
|
|
648
|
+
lines.push("");
|
|
649
|
+
}
|
|
650
|
+
return lines.join("\n");
|
|
651
|
+
}
|
|
652
|
+
function generateMigrationsFile(definitions) {
|
|
653
|
+
const persistentEntities = definitions.filter(isPersistentEntity);
|
|
654
|
+
if (persistentEntities.length === 0) {
|
|
655
|
+
return [
|
|
656
|
+
"/**",
|
|
657
|
+
" * AUTO-GENERATED - Do not edit manually",
|
|
658
|
+
" * Generated from EntityDefinition via @gzl10/nexus-sdk generators",
|
|
659
|
+
" */",
|
|
660
|
+
"",
|
|
661
|
+
"import type { ModuleContext } from '@gzl10/nexus-sdk'",
|
|
662
|
+
"",
|
|
663
|
+
"// No persistent entities to migrate",
|
|
664
|
+
"export async function migrate(_ctx: ModuleContext): Promise<void> {}",
|
|
665
|
+
""
|
|
666
|
+
].join("\n");
|
|
667
|
+
}
|
|
668
|
+
const lines = [
|
|
669
|
+
"/**",
|
|
670
|
+
" * AUTO-GENERATED - Do not edit manually",
|
|
671
|
+
" * Generated from EntityDefinition via @gzl10/nexus-sdk generators",
|
|
672
|
+
" */",
|
|
673
|
+
"",
|
|
674
|
+
"import type { ModuleContext, Knex } from '@gzl10/nexus-sdk'",
|
|
675
|
+
"",
|
|
676
|
+
"export async function migrate(ctx: ModuleContext): Promise<void> {",
|
|
677
|
+
" const { db, logger, helpers } = ctx",
|
|
678
|
+
" const { addTimestamps, addAuditFieldsIfMissing } = helpers",
|
|
679
|
+
""
|
|
680
|
+
];
|
|
681
|
+
for (const entity of persistentEntities) {
|
|
682
|
+
const { table, fields } = entity;
|
|
683
|
+
const timestamps = "timestamps" in entity ? entity.timestamps : false;
|
|
684
|
+
const audit = "audit" in entity ? entity.audit : false;
|
|
685
|
+
const indexes = "indexes" in entity ? entity.indexes : void 0;
|
|
686
|
+
const name = getEntityName(entity);
|
|
687
|
+
lines.push(` // === ${name.toUpperCase()} ===`);
|
|
688
|
+
lines.push(` if (!(await db.schema.hasTable('${table}'))) {`);
|
|
689
|
+
lines.push(` await db.schema.createTable('${table}', (table: Knex.CreateTableBuilder) => {`);
|
|
690
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
691
|
+
const columnCode = generateColumnCodeInline(fieldName, field);
|
|
692
|
+
lines.push(` ${columnCode}`);
|
|
693
|
+
}
|
|
694
|
+
if (timestamps) {
|
|
695
|
+
lines.push(` addTimestamps(table, db)`);
|
|
696
|
+
}
|
|
697
|
+
if (indexes?.length) {
|
|
698
|
+
lines.push("");
|
|
699
|
+
for (const idx of indexes) {
|
|
700
|
+
if (idx.unique) {
|
|
701
|
+
lines.push(` table.unique([${idx.columns.map((c) => `'${c}'`).join(", ")}])`);
|
|
702
|
+
} else {
|
|
703
|
+
lines.push(` table.index([${idx.columns.map((c) => `'${c}'`).join(", ")}])`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
lines.push(` })`);
|
|
708
|
+
lines.push(` logger.info('Created table: ${table}')`);
|
|
709
|
+
lines.push(` }`);
|
|
710
|
+
if (audit) {
|
|
711
|
+
lines.push(` await addAuditFieldsIfMissing(db, '${table}')`);
|
|
712
|
+
}
|
|
713
|
+
lines.push("");
|
|
714
|
+
}
|
|
715
|
+
lines.push("}");
|
|
716
|
+
lines.push("");
|
|
717
|
+
return lines.join("\n");
|
|
718
|
+
}
|
|
719
|
+
function generateColumnCodeInline(name, field) {
|
|
720
|
+
const { db } = field;
|
|
721
|
+
let code = "";
|
|
722
|
+
switch (db.type) {
|
|
723
|
+
case "string":
|
|
724
|
+
code = db.size ? `table.string('${name}', ${db.size})` : `table.string('${name}')`;
|
|
725
|
+
break;
|
|
726
|
+
case "text":
|
|
727
|
+
code = `table.text('${name}')`;
|
|
728
|
+
break;
|
|
729
|
+
case "integer":
|
|
730
|
+
code = `table.integer('${name}')`;
|
|
731
|
+
break;
|
|
732
|
+
case "decimal":
|
|
733
|
+
if (db.precision) {
|
|
734
|
+
code = `table.decimal('${name}', ${db.precision[0]}, ${db.precision[1]})`;
|
|
735
|
+
} else {
|
|
736
|
+
code = `table.decimal('${name}')`;
|
|
737
|
+
}
|
|
738
|
+
break;
|
|
739
|
+
case "boolean":
|
|
740
|
+
code = `table.boolean('${name}')`;
|
|
741
|
+
break;
|
|
742
|
+
case "date":
|
|
743
|
+
code = `table.date('${name}')`;
|
|
744
|
+
break;
|
|
745
|
+
case "datetime":
|
|
746
|
+
code = `table.datetime('${name}')`;
|
|
747
|
+
break;
|
|
748
|
+
case "json":
|
|
749
|
+
code = `table.json('${name}')`;
|
|
750
|
+
break;
|
|
751
|
+
case "uuid":
|
|
752
|
+
code = `table.uuid('${name}')`;
|
|
753
|
+
break;
|
|
754
|
+
default:
|
|
755
|
+
code = `table.string('${name}')`;
|
|
756
|
+
}
|
|
757
|
+
if (name === "id") {
|
|
758
|
+
code += ".primary()";
|
|
759
|
+
}
|
|
760
|
+
if (!db.nullable) {
|
|
761
|
+
code += ".notNullable()";
|
|
762
|
+
} else {
|
|
763
|
+
code += ".nullable()";
|
|
764
|
+
}
|
|
765
|
+
if (db.unique) {
|
|
766
|
+
code += ".unique()";
|
|
767
|
+
}
|
|
768
|
+
if (db.default !== void 0) {
|
|
769
|
+
if (typeof db.default === "string") {
|
|
770
|
+
code += `.defaultTo('${db.default}')`;
|
|
771
|
+
} else if (typeof db.default === "boolean") {
|
|
772
|
+
code += `.defaultTo(${db.default})`;
|
|
773
|
+
} else {
|
|
774
|
+
code += `.defaultTo(${db.default})`;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (db.index) {
|
|
778
|
+
code += ".index()";
|
|
779
|
+
}
|
|
780
|
+
if (field.relation) {
|
|
781
|
+
code += `.references('${field.relation.column ?? "id"}').inTable('${field.relation.table}')`;
|
|
782
|
+
if (field.relation.onDelete) {
|
|
783
|
+
code += `.onDelete('${field.relation.onDelete}')`;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return code;
|
|
787
|
+
}
|
|
444
788
|
export {
|
|
789
|
+
GENERATED_DIR,
|
|
445
790
|
generateCaslPermissions,
|
|
446
791
|
generateCaslSeed,
|
|
447
792
|
generateMigration,
|
|
793
|
+
generateMigrationsFile,
|
|
448
794
|
generateModel,
|
|
795
|
+
generateModelsFile,
|
|
796
|
+
generateReadOnlyModel,
|
|
797
|
+
generateReadOnlySchema,
|
|
798
|
+
generateSchemasFile,
|
|
449
799
|
generateZodSchema,
|
|
450
|
-
|
|
800
|
+
getEntityName,
|
|
801
|
+
getEntitySubject,
|
|
802
|
+
hasTable,
|
|
803
|
+
isPersistentEntity,
|
|
804
|
+
isSingletonEntity
|
|
451
805
|
};
|