@gzl10/nexus-sdk 0.10.0 → 0.11.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 +29 -38
- package/dist/index.js +59 -15
- package/package.json +2 -12
- package/dist/chunk-LQZB6HXM.js +0 -194
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +0 -155
package/dist/index.d.ts
CHANGED
|
@@ -5,14 +5,9 @@ export { CookieOptions, NextFunction, Request, RequestHandler, Response, Router
|
|
|
5
5
|
import { Logger } from 'pino';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Solo genera modelos TypeScript (interfaces).
|
|
11
|
-
* Schemas Zod y migraciones se generan en runtime por el backend.
|
|
8
|
+
* Type guards y utilidades para EntityDefinitions
|
|
12
9
|
*/
|
|
13
10
|
|
|
14
|
-
/** Directorio estándar para código generado */
|
|
15
|
-
declare const GENERATED_DIR = "__generated__";
|
|
16
11
|
/**
|
|
17
12
|
* Entidades que persisten en BD local con tabla propia
|
|
18
13
|
* Excluye: action, external, virtual, computed, single (usa tabla compartida)
|
|
@@ -34,23 +29,6 @@ declare function isSingletonEntity(entity: EntityDefinition): entity is SingleEn
|
|
|
34
29
|
* Type guard para verificar si una entidad tiene tabla propia (para migraciones)
|
|
35
30
|
*/
|
|
36
31
|
declare function hasTable(entity: EntityDefinition): entity is PersistentEntityDefinition;
|
|
37
|
-
/**
|
|
38
|
-
* Genera interface TypeScript desde EntityDefinition
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* const code = generateModel(postEntity)
|
|
42
|
-
* // Genera:
|
|
43
|
-
* // export interface Post {
|
|
44
|
-
* // id: string
|
|
45
|
-
* // title: string
|
|
46
|
-
* // ...
|
|
47
|
-
* // }
|
|
48
|
-
*/
|
|
49
|
-
declare function generateModel(entity: PersistentEntityDefinition): string;
|
|
50
|
-
/**
|
|
51
|
-
* Genera interface TypeScript para entidades read-only
|
|
52
|
-
*/
|
|
53
|
-
declare function generateReadOnlyModel(entity: ComputedEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition): string;
|
|
54
32
|
/**
|
|
55
33
|
* Obtiene el nombre de una entidad en PascalCase singular
|
|
56
34
|
* 'cms_posts' → 'Post', 'rol_role_permissions' → 'RolePermission'
|
|
@@ -60,15 +38,6 @@ declare function getEntityName(entity: EntityDefinition): string;
|
|
|
60
38
|
* Obtiene el subject CASL de una entidad
|
|
61
39
|
*/
|
|
62
40
|
declare function getEntitySubject(entity: PersistentEntityDefinition): string;
|
|
63
|
-
/**
|
|
64
|
-
* Genera archivo completo de modelos TypeScript para múltiples entidades
|
|
65
|
-
* Consolida todas las entidades de un módulo en un solo archivo
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* const code = generateModelsFile([userEntity, roleEntity])
|
|
69
|
-
* writeFileSync('users.models.ts', code)
|
|
70
|
-
*/
|
|
71
|
-
declare function generateModelsFile(definitions: EntityDefinition[]): string;
|
|
72
41
|
|
|
73
42
|
/**
|
|
74
43
|
* @gzl10/nexus-sdk
|
|
@@ -145,7 +114,7 @@ type DbType = 'string' | 'text' | 'integer' | 'decimal' | 'boolean' | 'date' | '
|
|
|
145
114
|
/**
|
|
146
115
|
* Tipos de input en UI
|
|
147
116
|
*/
|
|
148
|
-
type InputType = 'text' | 'email' | 'password' | 'url' | 'tel' | 'number' | 'decimal' | 'textarea' | 'markdown' | 'select' | 'multiselect' | 'checkbox' | 'switch' | 'date' | 'datetime' | 'file' | 'fileArray' | 'image' | 'imageArray' | 'hidden';
|
|
117
|
+
type InputType = 'text' | 'email' | 'password' | 'url' | 'tel' | 'number' | 'decimal' | 'textarea' | 'markdown' | 'select' | 'multiselect' | 'tags' | 'checkbox' | 'switch' | 'date' | 'datetime' | 'file' | 'fileArray' | 'image' | 'imageArray' | 'hidden';
|
|
149
118
|
/**
|
|
150
119
|
* Configuración de base de datos para un campo
|
|
151
120
|
*/
|
|
@@ -262,7 +231,7 @@ interface EntityIndex {
|
|
|
262
231
|
/**
|
|
263
232
|
* Acciones CASL estándar
|
|
264
233
|
*/
|
|
265
|
-
type CaslAction = 'manage' | 'create' | 'read' | 'update' | 'delete';
|
|
234
|
+
type CaslAction = 'manage' | 'create' | 'read' | 'update' | 'delete' | 'execute';
|
|
266
235
|
/**
|
|
267
236
|
* Condición de ownership para permisos
|
|
268
237
|
* El campo de la entidad que referencia al usuario
|
|
@@ -446,6 +415,8 @@ interface ExternalEntityDefinition {
|
|
|
446
415
|
ttl: number;
|
|
447
416
|
/** Campo para generar cache key */
|
|
448
417
|
key?: string;
|
|
418
|
+
/** Eventos que invalidan la cache (ej: ['db.users.*']) */
|
|
419
|
+
invalidateOn?: string[];
|
|
449
420
|
};
|
|
450
421
|
/** Autorización CASL */
|
|
451
422
|
casl?: EntityCaslConfig;
|
|
@@ -491,6 +462,8 @@ interface ComputedEntityDefinition {
|
|
|
491
462
|
cache?: {
|
|
492
463
|
/** TTL en segundos (0 = sin cache) */
|
|
493
464
|
ttl: number;
|
|
465
|
+
/** Eventos que invalidan la cache (ej: ['db.users.*']) */
|
|
466
|
+
invalidateOn?: string[];
|
|
494
467
|
};
|
|
495
468
|
/** Autorización CASL */
|
|
496
469
|
casl?: EntityCaslConfig;
|
|
@@ -959,6 +932,8 @@ interface ModuleManifest {
|
|
|
959
932
|
description?: string;
|
|
960
933
|
/** Tipo de módulo */
|
|
961
934
|
type?: 'core' | 'plugin' | 'auth-plugin' | 'custom';
|
|
935
|
+
/** Categoría del módulo para agrupar en sidebar */
|
|
936
|
+
category: Category;
|
|
962
937
|
/** Dependencias de otros módulos */
|
|
963
938
|
dependencies?: string[];
|
|
964
939
|
/** Requisitos para activar el módulo */
|
|
@@ -981,9 +956,25 @@ interface ModuleManifest {
|
|
|
981
956
|
definitions?: EntityDefinition[];
|
|
982
957
|
}
|
|
983
958
|
/**
|
|
984
|
-
* Categorías disponibles para plugins
|
|
959
|
+
* Categorías disponibles para plugins y módulos
|
|
960
|
+
*/
|
|
961
|
+
type Category = 'content' | 'data' | 'storage' | 'messaging' | 'jobs' | 'ai' | 'analytics' | 'integrations' | 'commerce' | 'security';
|
|
962
|
+
/**
|
|
963
|
+
* Metadata de una categoría
|
|
964
|
+
*/
|
|
965
|
+
interface CategoryMeta {
|
|
966
|
+
label: string;
|
|
967
|
+
icon: string;
|
|
968
|
+
order: number;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Registro centralizado de categorías con su metadata
|
|
972
|
+
*/
|
|
973
|
+
declare const CATEGORIES: Record<Category, CategoryMeta>;
|
|
974
|
+
/**
|
|
975
|
+
* Orden de categorías para sidebar (derivado de CATEGORIES)
|
|
985
976
|
*/
|
|
986
|
-
|
|
977
|
+
declare const CATEGORY_ORDER: Category[];
|
|
987
978
|
/**
|
|
988
979
|
* Manifest de un plugin Nexus
|
|
989
980
|
*/
|
|
@@ -997,7 +988,7 @@ interface PluginManifest {
|
|
|
997
988
|
/** Icono del plugin (Iconify MDI). Se hereda a los módulos si no lo definen */
|
|
998
989
|
icon?: string;
|
|
999
990
|
/** Categoría del plugin para agrupar en sidebar */
|
|
1000
|
-
category
|
|
991
|
+
category: Category;
|
|
1001
992
|
/** Versión del plugin (semver) */
|
|
1002
993
|
version: string;
|
|
1003
994
|
/** Descripción del plugin */
|
|
@@ -1006,4 +997,4 @@ interface PluginManifest {
|
|
|
1006
997
|
modules: ModuleManifest[];
|
|
1007
998
|
}
|
|
1008
999
|
|
|
1009
|
-
export { type AbilityLike, type ActionEntityDefinition, type ActionEntityService, type AuthRequest, type BaseEntityService, type BaseMailService, type BaseRolesService, type BaseUser, type BaseUsersService, type CaslAction, type CollectionEntityDefinition, type CollectionEntityService, type ComputedEntityDefinition, type ComputedEntityService, type ConfigEntityDefinition, type ConfigEntityService, type ContextHelpers, type CoreServices, type CreateEntityServiceOptions, type DbType, 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 FieldCaslAccess, type FieldDbConfig, type FieldDefinition, type FieldMeta, type FieldOptions, type FieldRelation, type FieldStorageConfig, type FieldValidationConfig, type ForbiddenErrorConstructor, type ForbiddenErrorInstance,
|
|
1000
|
+
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 ContextHelpers, type CoreServices, type CreateEntityServiceOptions, type DbType, 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 FieldCaslAccess, type FieldDbConfig, type FieldDefinition, type FieldMeta, type FieldOptions, type FieldRelation, type FieldStorageConfig, type FieldValidationConfig, type ForbiddenErrorConstructor, type ForbiddenErrorInstance, type InputType, type KnexAlterTableBuilder, type KnexCreateTableBuilder, type KnexTransaction, type LoggerReporter, type ModuleAbilities, type ModuleContext, type ModuleManifest, type ModuleMiddlewares, type ModuleRequirements, type NonPersistentEntityDefinition, type OwnershipCondition, type PaginatedResult, type PaginationParams, type PersistentEntityDefinition, type PluginAuthRequest, type PluginManifest, type ReferenceEntityDefinition, type ReferenceEntityService, type RolePermission, type SendMailOptions, type SendMailResult, type SingleEntityDefinition, type SingleEntityService, type TempEntityDefinition, type TempEntityService, type ValidateSchemas, type ValidationSchema, type ViewEntityDefinition, type ViewEntityService, type VirtualEntityDefinition, type VirtualEntityService, getEntityName, getEntitySubject, hasTable, isPersistentEntity, isSingletonEntity };
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
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
|
+
}
|
|
12
|
+
function getEntityName(entity) {
|
|
13
|
+
if ("table" in entity) {
|
|
14
|
+
const withoutPrefix = entity.table.replace(/^[a-z]{2,4}_/, "");
|
|
15
|
+
return toPascalCase(toSingular(withoutPrefix));
|
|
16
|
+
} else if ("key" in entity) {
|
|
17
|
+
return toPascalCase(entity.key);
|
|
18
|
+
} else {
|
|
19
|
+
return toSingular(entity.label).replace(/\s+/g, "");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function getEntitySubject(entity) {
|
|
23
|
+
return entity.casl?.subject ?? tableToSubject(entity.table);
|
|
24
|
+
}
|
|
25
|
+
function toPascalCase(str) {
|
|
26
|
+
return str.split("_").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
27
|
+
}
|
|
28
|
+
function toSingular(str) {
|
|
29
|
+
if (str.endsWith("ies")) return str.slice(0, -3) + "y";
|
|
30
|
+
if (str.endsWith("s")) return str.slice(0, -1);
|
|
31
|
+
return str;
|
|
32
|
+
}
|
|
33
|
+
function tableToSubject(table) {
|
|
34
|
+
const match = table.match(/^([a-z]{2,4})_(.+)$/);
|
|
35
|
+
if (!match) {
|
|
36
|
+
const withoutPrefix = table.replace(/^[a-z]{2,4}_/, "");
|
|
37
|
+
return toPascalCase(toSingular(withoutPrefix));
|
|
38
|
+
}
|
|
39
|
+
const [, prefix, rest] = match;
|
|
40
|
+
const prefixPascal = prefix.charAt(0).toUpperCase() + prefix.slice(1);
|
|
41
|
+
return prefixPascal + toPascalCase(toSingular(rest));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/index.ts
|
|
45
|
+
var CATEGORIES = {
|
|
46
|
+
data: { label: "Data", icon: "mdi:database", order: 1 },
|
|
47
|
+
content: { label: "Content", icon: "mdi:file-document-outline", order: 2 },
|
|
48
|
+
storage: { label: "Storage", icon: "mdi:folder-outline", order: 3 },
|
|
49
|
+
messaging: { label: "Messaging", icon: "mdi:message-outline", order: 4 },
|
|
50
|
+
jobs: { label: "Jobs", icon: "mdi:clock-outline", order: 5 },
|
|
51
|
+
ai: { label: "AI", icon: "mdi:robot-outline", order: 6 },
|
|
52
|
+
analytics: { label: "Analytics", icon: "mdi:chart-line", order: 7 },
|
|
53
|
+
integrations: { label: "Integrations", icon: "mdi:puzzle-outline", order: 8 },
|
|
54
|
+
commerce: { label: "Commerce", icon: "mdi:shopping-outline", order: 9 },
|
|
55
|
+
security: { label: "Security", icon: "mdi:shield-lock-outline", order: 10 }
|
|
56
|
+
};
|
|
57
|
+
var CATEGORY_ORDER = Object.keys(CATEGORIES).sort((a, b) => CATEGORIES[a].order - CATEGORIES[b].order);
|
|
12
58
|
export {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
generateModelsFile,
|
|
16
|
-
generateReadOnlyModel,
|
|
59
|
+
CATEGORIES,
|
|
60
|
+
CATEGORY_ORDER,
|
|
17
61
|
getEntityName,
|
|
18
62
|
getEntitySubject,
|
|
19
63
|
hasTable,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gzl10/nexus-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "SDK types for creating Nexus plugins and modules",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,9 +11,6 @@
|
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"bin": {
|
|
15
|
-
"nexus-sdk": "./dist/cli/index.js"
|
|
16
|
-
},
|
|
17
14
|
"files": [
|
|
18
15
|
"dist"
|
|
19
16
|
],
|
|
@@ -63,15 +60,8 @@
|
|
|
63
60
|
"access": "public",
|
|
64
61
|
"registry": "https://registry.npmjs.org"
|
|
65
62
|
},
|
|
66
|
-
"dependencies": {
|
|
67
|
-
"chokidar": "^5.0.0",
|
|
68
|
-
"commander": "^14.0.2",
|
|
69
|
-
"fast-glob": "^3.3.3",
|
|
70
|
-
"jiti": "^2.6.1",
|
|
71
|
-
"picocolors": "^1.1.1"
|
|
72
|
-
},
|
|
73
63
|
"scripts": {
|
|
74
|
-
"build": "tsup src/index.ts
|
|
64
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
75
65
|
"typecheck": "tsc --noEmit"
|
|
76
66
|
}
|
|
77
67
|
}
|
package/dist/chunk-LQZB6HXM.js
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
// src/generators.ts
|
|
2
|
-
var GENERATED_DIR = "__generated__";
|
|
3
|
-
function isPersistentEntity(entity) {
|
|
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";
|
|
12
|
-
}
|
|
13
|
-
function generateModel(entity) {
|
|
14
|
-
const { table, fields } = entity;
|
|
15
|
-
const timestamps = "timestamps" in entity ? entity.timestamps : false;
|
|
16
|
-
const audit = "audit" in entity ? entity.audit : false;
|
|
17
|
-
const entityName = tableToEntityName(table);
|
|
18
|
-
const lines = [
|
|
19
|
-
`/**`,
|
|
20
|
-
` * ${entity.label}`,
|
|
21
|
-
` * Generated from EntityDefinition`,
|
|
22
|
-
` */`,
|
|
23
|
-
`export interface ${entityName} {`
|
|
24
|
-
];
|
|
25
|
-
for (const [name, field] of Object.entries(fields)) {
|
|
26
|
-
const tsType = dbTypeToTsType(field.db);
|
|
27
|
-
const optional = field.db.nullable ? "?" : "";
|
|
28
|
-
lines.push(` ${name}${optional}: ${tsType}`);
|
|
29
|
-
}
|
|
30
|
-
if (timestamps) {
|
|
31
|
-
if (!fields["created_at"]) {
|
|
32
|
-
lines.push(` created_at: Date`);
|
|
33
|
-
}
|
|
34
|
-
if (!fields["updated_at"]) {
|
|
35
|
-
lines.push(` updated_at: Date`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
if (audit) {
|
|
39
|
-
if (!fields["created_by"]) {
|
|
40
|
-
lines.push(` created_by: string | null`);
|
|
41
|
-
}
|
|
42
|
-
if (!fields["updated_by"]) {
|
|
43
|
-
lines.push(` updated_by: string | null`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
lines.push(`}`);
|
|
47
|
-
lines.push(``);
|
|
48
|
-
return lines.join("\n");
|
|
49
|
-
}
|
|
50
|
-
function dbTypeToTsType(db) {
|
|
51
|
-
switch (db.type) {
|
|
52
|
-
case "string":
|
|
53
|
-
case "text":
|
|
54
|
-
case "uuid":
|
|
55
|
-
return "string";
|
|
56
|
-
case "integer":
|
|
57
|
-
case "decimal":
|
|
58
|
-
return "number";
|
|
59
|
-
case "boolean":
|
|
60
|
-
return "boolean";
|
|
61
|
-
case "date":
|
|
62
|
-
case "datetime":
|
|
63
|
-
return "Date";
|
|
64
|
-
case "json":
|
|
65
|
-
return "Record<string, unknown>";
|
|
66
|
-
default:
|
|
67
|
-
return "unknown";
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function generateReadOnlyModel(entity) {
|
|
71
|
-
const { label, fields } = entity;
|
|
72
|
-
const entityName = labelToEntityName(label);
|
|
73
|
-
const lines = [
|
|
74
|
-
`/**`,
|
|
75
|
-
` * ${label}`,
|
|
76
|
-
` * Generated from EntityDefinition (${entity.type})`,
|
|
77
|
-
` */`,
|
|
78
|
-
`export interface ${entityName} {`
|
|
79
|
-
];
|
|
80
|
-
for (const [name, field] of Object.entries(fields)) {
|
|
81
|
-
const tsType = dbTypeToTsType(field.db);
|
|
82
|
-
const optional = field.db.nullable ? "?" : "";
|
|
83
|
-
lines.push(` ${name}${optional}: ${tsType}`);
|
|
84
|
-
}
|
|
85
|
-
lines.push(`}`);
|
|
86
|
-
lines.push(``);
|
|
87
|
-
return lines.join("\n");
|
|
88
|
-
}
|
|
89
|
-
function getEntityName(entity) {
|
|
90
|
-
if ("table" in entity) {
|
|
91
|
-
const withoutPrefix = entity.table.replace(/^[a-z]{2,4}_/, "");
|
|
92
|
-
return toPascalCase(toSingular(withoutPrefix));
|
|
93
|
-
} else if ("key" in entity) {
|
|
94
|
-
return toPascalCase(entity.key);
|
|
95
|
-
} else {
|
|
96
|
-
return toSingular(entity.label).replace(/\s+/g, "");
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function getEntitySubject(entity) {
|
|
100
|
-
return entity.casl?.subject ?? tableToSubject(entity.table);
|
|
101
|
-
}
|
|
102
|
-
function dbTypeToTsSimple(type) {
|
|
103
|
-
switch (type) {
|
|
104
|
-
case "string":
|
|
105
|
-
case "text":
|
|
106
|
-
case "uuid":
|
|
107
|
-
return "string";
|
|
108
|
-
case "integer":
|
|
109
|
-
case "decimal":
|
|
110
|
-
return "number";
|
|
111
|
-
case "boolean":
|
|
112
|
-
return "boolean";
|
|
113
|
-
case "date":
|
|
114
|
-
case "datetime":
|
|
115
|
-
return "Date";
|
|
116
|
-
case "json":
|
|
117
|
-
return "Record<string, unknown>";
|
|
118
|
-
default:
|
|
119
|
-
return "unknown";
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function generateModelsFile(definitions) {
|
|
123
|
-
const lines = [
|
|
124
|
-
"/**",
|
|
125
|
-
" * AUTO-GENERATED - Do not edit manually",
|
|
126
|
-
" * Generated from EntityDefinition via @gzl10/nexus-sdk generators",
|
|
127
|
-
" */",
|
|
128
|
-
""
|
|
129
|
-
];
|
|
130
|
-
for (const entity of definitions) {
|
|
131
|
-
const name = getEntityName(entity);
|
|
132
|
-
lines.push(`// ============================================================================`);
|
|
133
|
-
lines.push(`// ${name.toUpperCase()} (${entity.type ?? "collection"})`);
|
|
134
|
-
lines.push(`// ============================================================================`);
|
|
135
|
-
lines.push("");
|
|
136
|
-
if (isPersistentEntity(entity)) {
|
|
137
|
-
const model = generateModel(entity);
|
|
138
|
-
const modelLines = model.split("\n");
|
|
139
|
-
lines.push(...modelLines);
|
|
140
|
-
} else if (entity.type === "single") {
|
|
141
|
-
lines.push(`/**`);
|
|
142
|
-
lines.push(` * ${entity.label}`);
|
|
143
|
-
lines.push(` * Generated from EntityDefinition (single)`);
|
|
144
|
-
lines.push(` */`);
|
|
145
|
-
lines.push(`export interface ${name} {`);
|
|
146
|
-
for (const [fieldName, field] of Object.entries(entity.fields)) {
|
|
147
|
-
const tsType = dbTypeToTsSimple(field.db.type);
|
|
148
|
-
const optional = field.db.nullable ? "?" : "";
|
|
149
|
-
lines.push(` ${fieldName}${optional}: ${tsType}`);
|
|
150
|
-
}
|
|
151
|
-
lines.push("}");
|
|
152
|
-
} else {
|
|
153
|
-
const model = generateReadOnlyModel(entity);
|
|
154
|
-
lines.push(model);
|
|
155
|
-
}
|
|
156
|
-
lines.push("");
|
|
157
|
-
}
|
|
158
|
-
return lines.join("\n");
|
|
159
|
-
}
|
|
160
|
-
function toPascalCase(str) {
|
|
161
|
-
return str.split("_").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
162
|
-
}
|
|
163
|
-
function toSingular(str) {
|
|
164
|
-
if (str.endsWith("ies")) return str.slice(0, -3) + "y";
|
|
165
|
-
if (str.endsWith("s")) return str.slice(0, -1);
|
|
166
|
-
return str;
|
|
167
|
-
}
|
|
168
|
-
function tableToEntityName(table) {
|
|
169
|
-
const withoutPrefix = table.replace(/^[a-z]{2,4}_/, "");
|
|
170
|
-
return toPascalCase(toSingular(withoutPrefix));
|
|
171
|
-
}
|
|
172
|
-
function tableToSubject(table) {
|
|
173
|
-
const match = table.match(/^([a-z]{2,4})_(.+)$/);
|
|
174
|
-
if (!match) return tableToEntityName(table);
|
|
175
|
-
const [, prefix, rest] = match;
|
|
176
|
-
const prefixPascal = prefix.charAt(0).toUpperCase() + prefix.slice(1);
|
|
177
|
-
return prefixPascal + toPascalCase(toSingular(rest));
|
|
178
|
-
}
|
|
179
|
-
function labelToEntityName(label) {
|
|
180
|
-
const singular = label.endsWith("ies") ? label.slice(0, -3) + "y" : label.endsWith("s") ? label.slice(0, -1) : label;
|
|
181
|
-
return singular.charAt(0).toUpperCase() + singular.slice(1).replace(/\s+/g, "");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export {
|
|
185
|
-
GENERATED_DIR,
|
|
186
|
-
isPersistentEntity,
|
|
187
|
-
isSingletonEntity,
|
|
188
|
-
hasTable,
|
|
189
|
-
generateModel,
|
|
190
|
-
generateReadOnlyModel,
|
|
191
|
-
getEntityName,
|
|
192
|
-
getEntitySubject,
|
|
193
|
-
generateModelsFile
|
|
194
|
-
};
|
package/dist/cli/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
package/dist/cli/index.js
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
GENERATED_DIR,
|
|
4
|
-
generateModelsFile
|
|
5
|
-
} from "../chunk-LQZB6HXM.js";
|
|
6
|
-
|
|
7
|
-
// src/cli/index.ts
|
|
8
|
-
import { Command as Command2 } from "commander";
|
|
9
|
-
|
|
10
|
-
// src/cli/commands/generate.ts
|
|
11
|
-
import { Command } from "commander";
|
|
12
|
-
import { join, dirname } from "path";
|
|
13
|
-
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
14
|
-
import pc from "picocolors";
|
|
15
|
-
import { watch } from "chokidar";
|
|
16
|
-
|
|
17
|
-
// src/cli/discovery/module-finder.ts
|
|
18
|
-
import fg from "fast-glob";
|
|
19
|
-
async function findModules(opts) {
|
|
20
|
-
const pattern = opts.module ? `**/${opts.module}/index.ts` : "**/index.ts";
|
|
21
|
-
const files = await fg(pattern, {
|
|
22
|
-
cwd: opts.path,
|
|
23
|
-
absolute: true,
|
|
24
|
-
ignore: [
|
|
25
|
-
"**/node_modules/**",
|
|
26
|
-
"**/dist/**",
|
|
27
|
-
"**/__generated__/**",
|
|
28
|
-
"index.ts"
|
|
29
|
-
// Exclude root modules/index.ts (not nested ones)
|
|
30
|
-
]
|
|
31
|
-
});
|
|
32
|
-
return files.filter((file) => {
|
|
33
|
-
const parts = file.split("/");
|
|
34
|
-
const fileName = parts.pop();
|
|
35
|
-
const folderName = parts.pop();
|
|
36
|
-
return fileName === "index.ts" && folderName && !folderName.startsWith("_");
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// src/cli/discovery/manifest-extractor.ts
|
|
41
|
-
import { createJiti } from "jiti";
|
|
42
|
-
var jiti = createJiti(import.meta.url, {
|
|
43
|
-
interopDefault: true,
|
|
44
|
-
moduleCache: false
|
|
45
|
-
// Don't cache for watch mode
|
|
46
|
-
});
|
|
47
|
-
function isModuleManifest(value) {
|
|
48
|
-
return typeof value === "object" && value !== null && "name" in value && typeof value.name === "string";
|
|
49
|
-
}
|
|
50
|
-
async function extractManifest(modulePath, verbose = false) {
|
|
51
|
-
try {
|
|
52
|
-
const mod = await jiti.import(modulePath);
|
|
53
|
-
for (const value of Object.values(mod)) {
|
|
54
|
-
if (isModuleManifest(value)) {
|
|
55
|
-
const manifest = value;
|
|
56
|
-
if (manifest.definitions?.length) {
|
|
57
|
-
return {
|
|
58
|
-
path: modulePath,
|
|
59
|
-
manifest,
|
|
60
|
-
definitions: manifest.definitions
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
} catch (error) {
|
|
67
|
-
if (verbose) {
|
|
68
|
-
console.error(`Error loading ${modulePath}: ${getErrorMessage(error)}`);
|
|
69
|
-
}
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
function getErrorMessage(error) {
|
|
74
|
-
if (error instanceof Error) {
|
|
75
|
-
return error.message;
|
|
76
|
-
}
|
|
77
|
-
return String(error);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// src/cli/commands/generate.ts
|
|
81
|
-
async function runGenerate(opts) {
|
|
82
|
-
const startTime = Date.now();
|
|
83
|
-
console.log(pc.blue("Nexus SDK Generator"));
|
|
84
|
-
console.log(pc.dim(`Scanning ${opts.path}...
|
|
85
|
-
`));
|
|
86
|
-
const modulePaths = await findModules({ path: opts.path, module: opts.module });
|
|
87
|
-
if (opts.verbose) {
|
|
88
|
-
console.log(pc.dim(`Found ${modulePaths.length} potential module(s)`));
|
|
89
|
-
}
|
|
90
|
-
if (modulePaths.length === 0) {
|
|
91
|
-
console.log(pc.yellow("No modules found"));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const modules = [];
|
|
95
|
-
for (const path of modulePaths) {
|
|
96
|
-
const extracted = await extractManifest(path, opts.verbose);
|
|
97
|
-
if (extracted) {
|
|
98
|
-
modules.push(extracted);
|
|
99
|
-
if (opts.verbose) {
|
|
100
|
-
console.log(pc.dim(` \u2713 ${extracted.manifest.name}: ${extracted.definitions.length} entities`));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (modules.length === 0) {
|
|
105
|
-
console.log(pc.yellow("No modules with definitions found"));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
let totalFiles = 0;
|
|
109
|
-
for (const mod of modules) {
|
|
110
|
-
const moduleDir = dirname(mod.path);
|
|
111
|
-
const outputDir = join(moduleDir, opts.output || GENERATED_DIR);
|
|
112
|
-
if (!opts.dryRun && !existsSync(outputDir)) {
|
|
113
|
-
mkdirSync(outputDir, { recursive: true });
|
|
114
|
-
}
|
|
115
|
-
const content = generateModelsFile(mod.definitions);
|
|
116
|
-
const filePath = join(outputDir, `${mod.manifest.name}.models.ts`);
|
|
117
|
-
if (!opts.dryRun) writeFileSync(filePath, content);
|
|
118
|
-
totalFiles++;
|
|
119
|
-
const prefix = opts.dryRun ? pc.yellow("[dry-run]") : pc.green("\u2713");
|
|
120
|
-
console.log(`${prefix} ${pc.bold(mod.manifest.name)}: models`);
|
|
121
|
-
}
|
|
122
|
-
const elapsed = Date.now() - startTime;
|
|
123
|
-
console.log(
|
|
124
|
-
`
|
|
125
|
-
${pc.green("\u2728")} Generated ${totalFiles} file(s) in ${modules.length} module(s) (${elapsed}ms)`
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
function createGenerateCommand() {
|
|
129
|
-
return new Command("generate").description("Generate TypeScript models from EntityDefinitions").option("-p, --path <dir>", "Base modules directory", "src/modules").option("-m, --module <name>", "Generate only specific module").option("-o, --output <dir>", "Output directory name", "__generated__").option("-w, --watch", "Watch for changes and regenerate").option("--dry-run", "Show what would be generated").option("--verbose", "Show detailed logs").action(async (opts) => {
|
|
130
|
-
await runGenerate(opts);
|
|
131
|
-
if (opts.watch) {
|
|
132
|
-
console.log(pc.blue("\nWatching for changes..."));
|
|
133
|
-
const watcher = watch(["**/*.entity.ts", "**/index.ts"], {
|
|
134
|
-
cwd: opts.path,
|
|
135
|
-
ignored: ["**/node_modules/**", "**/__generated__/**", "**/dist/**"],
|
|
136
|
-
ignoreInitial: true
|
|
137
|
-
});
|
|
138
|
-
watcher.on("change", async (path) => {
|
|
139
|
-
console.log(pc.dim(`
|
|
140
|
-
Change detected: ${path}`));
|
|
141
|
-
await runGenerate({ ...opts, watch: false });
|
|
142
|
-
});
|
|
143
|
-
watcher.on("add", async (path) => {
|
|
144
|
-
console.log(pc.dim(`
|
|
145
|
-
New file: ${path}`));
|
|
146
|
-
await runGenerate({ ...opts, watch: false });
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// src/cli/index.ts
|
|
153
|
-
var program = new Command2().name("nexus-sdk").description("Nexus SDK CLI - Code generation from EntityDefinitions").version("0.10.0");
|
|
154
|
-
program.addCommand(createGenerateCommand());
|
|
155
|
-
program.parse();
|