@gzl10/nexus-sdk 0.3.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/dist/index.d.ts CHANGED
@@ -12,10 +12,26 @@ import { Logger } from 'pino';
12
12
  */
13
13
 
14
14
  /**
15
- * Entidades que persisten en BD local
16
- * Excluye: action, external, virtual, computed
15
+ * Entidades que persisten en BD local con tabla propia
16
+ * Excluye: action, external, virtual, computed, single (usa tabla compartida)
17
17
  */
18
- type PersistentEntityDefinition = CollectionEntityDefinition | SingleEntityDefinition | ReferenceEntityDefinition | EventEntityDefinition | ConfigEntityDefinition | TempEntityDefinition | ViewEntityDefinition;
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;
19
35
  /**
20
36
  * Genera código de migración Knex desde EntityDefinition
21
37
  *
@@ -51,6 +67,15 @@ declare function generateZodSchema(entity: PersistentEntityDefinition): string;
51
67
  * // }
52
68
  */
53
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;
54
79
  /**
55
80
  * Estructura de permiso generado para insertar en BD
56
81
  */
@@ -82,6 +107,29 @@ declare function generateCaslSeed(entities: EntityDefinition[]): string;
82
107
  * Obtiene el subject CASL de una entidad
83
108
  */
84
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;
85
133
 
86
134
  /**
87
135
  * @gzl10/nexus-sdk
@@ -370,14 +418,24 @@ interface CollectionEntityDefinition extends BaseEntityDefinition {
370
418
  indexes?: EntityIndex[];
371
419
  }
372
420
  /**
373
- * Entidad singleton - Solo Update/Read, sin lista (site_config)
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: '...' } }
374
428
  */
375
- interface SingleEntityDefinition extends BaseEntityDefinition {
429
+ interface SingleEntityDefinition {
376
430
  type: 'single';
377
- /** Añadir updated_at */
378
- timestamps?: boolean;
379
- /** Añadir updated_by */
380
- audit?: boolean;
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;
381
439
  }
382
440
  /**
383
441
  * Entidad de referencia - Catálogos con CRUD admin (countries, currencies)
@@ -723,4 +781,4 @@ interface PluginManifest {
723
781
  modules: ModuleManifest[];
724
782
  }
725
783
 
726
- 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 OwnershipCondition, type PaginatedResult, type PaginationParams, 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, generateZodSchema, getEntitySubject };
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,7 +1,13 @@
1
1
  // src/generators.ts
2
2
  function isPersistentEntity(entity) {
3
- const nonPersistent = ["action", "external", "virtual", "computed"];
4
- return !nonPersistent.includes(entity.type ?? "collection");
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";
5
11
  }
6
12
  function generateMigration(entity) {
7
13
  const { table, fields } = entity;
@@ -291,19 +297,81 @@ function dbTypeToTsType(db) {
291
297
  return "unknown";
292
298
  }
293
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
+ }
294
308
  function tableToEntityName(table) {
295
309
  const withoutPrefix = table.replace(/^[a-z]{2,4}_/, "");
296
- const singular = withoutPrefix.endsWith("ies") ? withoutPrefix.slice(0, -3) + "y" : withoutPrefix.endsWith("s") ? withoutPrefix.slice(0, -1) : withoutPrefix;
297
- return singular.charAt(0).toUpperCase() + singular.slice(1);
310
+ return toPascalCase(toSingular(withoutPrefix));
298
311
  }
299
312
  function tableToSubject(table) {
300
313
  const match = table.match(/^([a-z]{2,4})_(.+)$/);
301
314
  if (!match) return tableToEntityName(table);
302
315
  const [, prefix, rest] = match;
303
316
  const prefixPascal = prefix.charAt(0).toUpperCase() + prefix.slice(1);
304
- const singular = rest.endsWith("ies") ? rest.slice(0, -3) + "y" : rest.endsWith("s") ? rest.slice(0, -1) : rest;
305
- const restPascal = singular.charAt(0).toUpperCase() + singular.slice(1);
306
- return prefixPascal + restPascal;
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");
307
375
  }
308
376
  function getFieldsForRole(entity, role, action) {
309
377
  const { fields, casl } = entity;
@@ -441,11 +509,158 @@ function generateCaslSeed(entities) {
441
509
  function getEntitySubject(entity) {
442
510
  return entity.casl?.subject ?? tableToSubject(entity.table);
443
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
+ }
444
651
  export {
445
652
  generateCaslPermissions,
446
653
  generateCaslSeed,
447
654
  generateMigration,
448
655
  generateModel,
656
+ generateModelsFile,
657
+ generateReadOnlyModel,
658
+ generateReadOnlySchema,
659
+ generateSchemasFile,
449
660
  generateZodSchema,
450
- getEntitySubject
661
+ getEntityName,
662
+ getEntitySubject,
663
+ hasTable,
664
+ isPersistentEntity,
665
+ isSingletonEntity
451
666
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gzl10/nexus-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "SDK types for creating Nexus plugins and modules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",