@gzl10/nexus-sdk 0.5.0 → 0.6.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 +10 -27
- package/dist/chunk-UZT5CTOT.js +186 -0
- package/dist/cli/index.js +10 -35
- package/dist/index.d.ts +22 -77
- package/dist/index.js +1 -15
- package/package.json +1 -1
- package/dist/chunk-XEPNN6IG.js +0 -806
package/README.md
CHANGED
|
@@ -160,35 +160,25 @@ export const postEntity: EntityDefinition = {
|
|
|
160
160
|
|
|
161
161
|
### Code Generators
|
|
162
162
|
|
|
163
|
-
Generate
|
|
163
|
+
Generate TypeScript interfaces from EntityDefinitions:
|
|
164
164
|
|
|
165
165
|
```typescript
|
|
166
|
-
import {
|
|
167
|
-
generateMigration,
|
|
168
|
-
generateZodSchema,
|
|
169
|
-
generateModel,
|
|
170
|
-
generateCaslPermissions
|
|
171
|
-
} from '@gzl10/nexus-sdk'
|
|
166
|
+
import { generateModel, generateModelsFile } from '@gzl10/nexus-sdk'
|
|
172
167
|
|
|
173
|
-
// Generate
|
|
174
|
-
const migrationCode = generateMigration(postEntity)
|
|
175
|
-
|
|
176
|
-
// Generate Zod validation schemas
|
|
177
|
-
const zodCode = generateZodSchema(postEntity)
|
|
178
|
-
// Creates: createPostSchema, updatePostSchema, postParamsSchema, postQuerySchema
|
|
179
|
-
|
|
180
|
-
// Generate TypeScript interface
|
|
168
|
+
// Generate TypeScript interface for a single entity
|
|
181
169
|
const modelCode = generateModel(postEntity)
|
|
182
170
|
// Creates: export interface Post { ... }
|
|
183
171
|
|
|
184
|
-
// Generate
|
|
185
|
-
const
|
|
186
|
-
//
|
|
172
|
+
// Generate models file for multiple entities
|
|
173
|
+
const modelsFile = generateModelsFile([postEntity, userEntity])
|
|
174
|
+
// Creates consolidated models file with all interfaces
|
|
187
175
|
```
|
|
188
176
|
|
|
177
|
+
> **Note:** Zod schemas and migrations are now generated at runtime by `@gzl10/nexus-backend`.
|
|
178
|
+
|
|
189
179
|
## CLI (v0.5.0+)
|
|
190
180
|
|
|
191
|
-
|
|
181
|
+
Generate TypeScript models from EntityDefinitions in your modules:
|
|
192
182
|
|
|
193
183
|
```bash
|
|
194
184
|
# Install globally or use npx
|
|
@@ -216,10 +206,6 @@ nexus-sdk generate --dry-run --verbose
|
|
|
216
206
|
| `-o, --output <dir>` | `__generated__` | Output directory name |
|
|
217
207
|
| `-w, --watch` | - | Watch for changes and regenerate |
|
|
218
208
|
| `--dry-run` | - | Show what would be generated |
|
|
219
|
-
| `--skip-schemas` | - | Skip `.schemas.ts` generation |
|
|
220
|
-
| `--skip-models` | - | Skip `.models.ts` generation |
|
|
221
|
-
| `--skip-migrations` | - | Skip `.migrate.ts` generation |
|
|
222
|
-
| `--skip-casl` | - | Skip `.casl.ts` generation |
|
|
223
209
|
| `--verbose` | - | Show detailed logs |
|
|
224
210
|
|
|
225
211
|
### Generated Files
|
|
@@ -230,10 +216,7 @@ For each module with `definitions`, the CLI generates:
|
|
|
230
216
|
src/modules/posts/
|
|
231
217
|
├── index.ts # Module manifest with definitions
|
|
232
218
|
└── __generated__/
|
|
233
|
-
|
|
234
|
-
├── posts.models.ts # TypeScript interfaces
|
|
235
|
-
├── posts.migrate.ts # Knex migration function
|
|
236
|
-
└── posts.casl.ts # CASL permission seeds
|
|
219
|
+
└── posts.models.ts # TypeScript interfaces
|
|
237
220
|
```
|
|
238
221
|
|
|
239
222
|
### Module Structure
|
|
@@ -0,0 +1,186 @@
|
|
|
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
|
+
lines.push(` created_at: Date`);
|
|
32
|
+
lines.push(` updated_at: Date`);
|
|
33
|
+
}
|
|
34
|
+
if (audit) {
|
|
35
|
+
lines.push(` created_by: string | null`);
|
|
36
|
+
lines.push(` updated_by: string | null`);
|
|
37
|
+
}
|
|
38
|
+
lines.push(`}`);
|
|
39
|
+
lines.push(``);
|
|
40
|
+
return lines.join("\n");
|
|
41
|
+
}
|
|
42
|
+
function dbTypeToTsType(db) {
|
|
43
|
+
switch (db.type) {
|
|
44
|
+
case "string":
|
|
45
|
+
case "text":
|
|
46
|
+
case "uuid":
|
|
47
|
+
return "string";
|
|
48
|
+
case "integer":
|
|
49
|
+
case "decimal":
|
|
50
|
+
return "number";
|
|
51
|
+
case "boolean":
|
|
52
|
+
return "boolean";
|
|
53
|
+
case "date":
|
|
54
|
+
case "datetime":
|
|
55
|
+
return "Date";
|
|
56
|
+
case "json":
|
|
57
|
+
return "Record<string, unknown>";
|
|
58
|
+
default:
|
|
59
|
+
return "unknown";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function generateReadOnlyModel(entity) {
|
|
63
|
+
const { label, fields } = entity;
|
|
64
|
+
const entityName = labelToEntityName(label);
|
|
65
|
+
const lines = [
|
|
66
|
+
`/**`,
|
|
67
|
+
` * ${label}`,
|
|
68
|
+
` * Generated from EntityDefinition (${entity.type})`,
|
|
69
|
+
` */`,
|
|
70
|
+
`export interface ${entityName} {`
|
|
71
|
+
];
|
|
72
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
73
|
+
const tsType = dbTypeToTsType(field.db);
|
|
74
|
+
const optional = field.db.nullable ? "?" : "";
|
|
75
|
+
lines.push(` ${name}${optional}: ${tsType}`);
|
|
76
|
+
}
|
|
77
|
+
lines.push(`}`);
|
|
78
|
+
lines.push(``);
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
function getEntityName(entity) {
|
|
82
|
+
if ("table" in entity) {
|
|
83
|
+
const withoutPrefix = entity.table.replace(/^[a-z]{2,4}_/, "");
|
|
84
|
+
return toPascalCase(toSingular(withoutPrefix));
|
|
85
|
+
} else if ("key" in entity) {
|
|
86
|
+
return toPascalCase(entity.key);
|
|
87
|
+
} else {
|
|
88
|
+
return toSingular(entity.label).replace(/\s+/g, "");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function getEntitySubject(entity) {
|
|
92
|
+
return entity.casl?.subject ?? tableToSubject(entity.table);
|
|
93
|
+
}
|
|
94
|
+
function dbTypeToTsSimple(type) {
|
|
95
|
+
switch (type) {
|
|
96
|
+
case "string":
|
|
97
|
+
case "text":
|
|
98
|
+
case "uuid":
|
|
99
|
+
return "string";
|
|
100
|
+
case "integer":
|
|
101
|
+
case "decimal":
|
|
102
|
+
return "number";
|
|
103
|
+
case "boolean":
|
|
104
|
+
return "boolean";
|
|
105
|
+
case "date":
|
|
106
|
+
case "datetime":
|
|
107
|
+
return "Date";
|
|
108
|
+
case "json":
|
|
109
|
+
return "Record<string, unknown>";
|
|
110
|
+
default:
|
|
111
|
+
return "unknown";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function generateModelsFile(definitions) {
|
|
115
|
+
const lines = [
|
|
116
|
+
"/**",
|
|
117
|
+
" * AUTO-GENERATED - Do not edit manually",
|
|
118
|
+
" * Generated from EntityDefinition via @gzl10/nexus-sdk generators",
|
|
119
|
+
" */",
|
|
120
|
+
""
|
|
121
|
+
];
|
|
122
|
+
for (const entity of definitions) {
|
|
123
|
+
const name = getEntityName(entity);
|
|
124
|
+
lines.push(`// ============================================================================`);
|
|
125
|
+
lines.push(`// ${name.toUpperCase()} (${entity.type ?? "collection"})`);
|
|
126
|
+
lines.push(`// ============================================================================`);
|
|
127
|
+
lines.push("");
|
|
128
|
+
if (isPersistentEntity(entity)) {
|
|
129
|
+
const model = generateModel(entity);
|
|
130
|
+
const modelLines = model.split("\n");
|
|
131
|
+
lines.push(...modelLines);
|
|
132
|
+
} else if (entity.type === "single") {
|
|
133
|
+
lines.push(`/**`);
|
|
134
|
+
lines.push(` * ${entity.label}`);
|
|
135
|
+
lines.push(` * Generated from EntityDefinition (single)`);
|
|
136
|
+
lines.push(` */`);
|
|
137
|
+
lines.push(`export interface ${name} {`);
|
|
138
|
+
for (const [fieldName, field] of Object.entries(entity.fields)) {
|
|
139
|
+
const tsType = dbTypeToTsSimple(field.db.type);
|
|
140
|
+
const optional = field.db.nullable ? "?" : "";
|
|
141
|
+
lines.push(` ${fieldName}${optional}: ${tsType}`);
|
|
142
|
+
}
|
|
143
|
+
lines.push("}");
|
|
144
|
+
} else {
|
|
145
|
+
const model = generateReadOnlyModel(entity);
|
|
146
|
+
lines.push(model);
|
|
147
|
+
}
|
|
148
|
+
lines.push("");
|
|
149
|
+
}
|
|
150
|
+
return lines.join("\n");
|
|
151
|
+
}
|
|
152
|
+
function toPascalCase(str) {
|
|
153
|
+
return str.split("_").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
154
|
+
}
|
|
155
|
+
function toSingular(str) {
|
|
156
|
+
if (str.endsWith("ies")) return str.slice(0, -3) + "y";
|
|
157
|
+
if (str.endsWith("s")) return str.slice(0, -1);
|
|
158
|
+
return str;
|
|
159
|
+
}
|
|
160
|
+
function tableToEntityName(table) {
|
|
161
|
+
const withoutPrefix = table.replace(/^[a-z]{2,4}_/, "");
|
|
162
|
+
return toPascalCase(toSingular(withoutPrefix));
|
|
163
|
+
}
|
|
164
|
+
function tableToSubject(table) {
|
|
165
|
+
const match = table.match(/^([a-z]{2,4})_(.+)$/);
|
|
166
|
+
if (!match) return tableToEntityName(table);
|
|
167
|
+
const [, prefix, rest] = match;
|
|
168
|
+
const prefixPascal = prefix.charAt(0).toUpperCase() + prefix.slice(1);
|
|
169
|
+
return prefixPascal + toPascalCase(toSingular(rest));
|
|
170
|
+
}
|
|
171
|
+
function labelToEntityName(label) {
|
|
172
|
+
const singular = label.endsWith("ies") ? label.slice(0, -3) + "y" : label.endsWith("s") ? label.slice(0, -1) : label;
|
|
173
|
+
return singular.charAt(0).toUpperCase() + singular.slice(1).replace(/\s+/g, "");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export {
|
|
177
|
+
GENERATED_DIR,
|
|
178
|
+
isPersistentEntity,
|
|
179
|
+
isSingletonEntity,
|
|
180
|
+
hasTable,
|
|
181
|
+
generateModel,
|
|
182
|
+
generateReadOnlyModel,
|
|
183
|
+
getEntityName,
|
|
184
|
+
getEntitySubject,
|
|
185
|
+
generateModelsFile
|
|
186
|
+
};
|
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
GENERATED_DIR,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
generateModelsFile,
|
|
7
|
-
generateSchemasFile
|
|
8
|
-
} from "../chunk-XEPNN6IG.js";
|
|
4
|
+
generateModelsFile
|
|
5
|
+
} from "../chunk-UZT5CTOT.js";
|
|
9
6
|
|
|
10
7
|
// src/cli/index.ts
|
|
11
8
|
import { Command as Command2 } from "commander";
|
|
@@ -115,43 +112,21 @@ async function runGenerate(opts) {
|
|
|
115
112
|
if (!opts.dryRun && !existsSync(outputDir)) {
|
|
116
113
|
mkdirSync(outputDir, { recursive: true });
|
|
117
114
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!opts.dryRun) writeFileSync(filePath, content);
|
|
123
|
-
files.push("schemas");
|
|
124
|
-
}
|
|
125
|
-
if (!opts.skipModels) {
|
|
126
|
-
const content = generateModelsFile(mod.definitions);
|
|
127
|
-
const filePath = join(outputDir, `${mod.manifest.name}.models.ts`);
|
|
128
|
-
if (!opts.dryRun) writeFileSync(filePath, content);
|
|
129
|
-
files.push("models");
|
|
130
|
-
}
|
|
131
|
-
if (!opts.skipMigrations) {
|
|
132
|
-
const content = generateMigrationsFile(mod.definitions);
|
|
133
|
-
const filePath = join(outputDir, `${mod.manifest.name}.migrate.ts`);
|
|
134
|
-
if (!opts.dryRun) writeFileSync(filePath, content);
|
|
135
|
-
files.push("migrate");
|
|
136
|
-
}
|
|
137
|
-
if (!opts.skipCasl) {
|
|
138
|
-
const content = generateCaslSeed(mod.definitions);
|
|
139
|
-
const filePath = join(outputDir, `${mod.manifest.name}.casl.ts`);
|
|
140
|
-
if (!opts.dryRun) writeFileSync(filePath, content);
|
|
141
|
-
files.push("casl");
|
|
142
|
-
}
|
|
143
|
-
totalFiles += files.length;
|
|
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++;
|
|
144
119
|
const prefix = opts.dryRun ? pc.yellow("[dry-run]") : pc.green("\u2713");
|
|
145
|
-
console.log(`${prefix} ${pc.bold(mod.manifest.name)}:
|
|
120
|
+
console.log(`${prefix} ${pc.bold(mod.manifest.name)}: models`);
|
|
146
121
|
}
|
|
147
122
|
const elapsed = Date.now() - startTime;
|
|
148
123
|
console.log(
|
|
149
124
|
`
|
|
150
|
-
${pc.green("\u2728")} Generated ${totalFiles}
|
|
125
|
+
${pc.green("\u2728")} Generated ${totalFiles} file(s) in ${modules.length} module(s) (${elapsed}ms)`
|
|
151
126
|
);
|
|
152
127
|
}
|
|
153
128
|
function createGenerateCommand() {
|
|
154
|
-
return new Command("generate").description("Generate
|
|
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) => {
|
|
155
130
|
await runGenerate(opts);
|
|
156
131
|
if (opts.watch) {
|
|
157
132
|
console.log(pc.blue("\nWatching for changes..."));
|
|
@@ -175,6 +150,6 @@ New file: ${path}`));
|
|
|
175
150
|
}
|
|
176
151
|
|
|
177
152
|
// src/cli/index.ts
|
|
178
|
-
var program = new Command2().name("nexus-sdk").description("Nexus SDK CLI - Code generation from EntityDefinitions").version("0.
|
|
153
|
+
var program = new Command2().name("nexus-sdk").description("Nexus SDK CLI - Code generation from EntityDefinitions").version("0.6.0");
|
|
179
154
|
program.addCommand(createGenerateCommand());
|
|
180
155
|
program.parse();
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ import { Logger } from 'pino';
|
|
|
7
7
|
/**
|
|
8
8
|
* Generadores de código desde EntityDefinition
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Solo genera modelos TypeScript (interfaces).
|
|
11
|
+
* Schemas Zod y migraciones se generan en runtime por el backend.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/** Directorio estándar para código generado */
|
|
@@ -34,28 +34,6 @@ declare function isSingletonEntity(entity: EntityDefinition): entity is SingleEn
|
|
|
34
34
|
* Type guard para verificar si una entidad tiene tabla propia (para migraciones)
|
|
35
35
|
*/
|
|
36
36
|
declare function hasTable(entity: EntityDefinition): entity is PersistentEntityDefinition;
|
|
37
|
-
/**
|
|
38
|
-
* Genera código de migración Knex desde EntityDefinition
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* const code = generateMigration(postEntity)
|
|
42
|
-
* // Genera:
|
|
43
|
-
* // import type { ModuleContext, Knex } from '@gzl10/nexus-sdk'
|
|
44
|
-
* //
|
|
45
|
-
* // export async function migrate(ctx: ModuleContext): Promise<void> {
|
|
46
|
-
* // const { db, logger, helpers } = ctx
|
|
47
|
-
* // ...
|
|
48
|
-
* // }
|
|
49
|
-
*/
|
|
50
|
-
declare function generateMigration(entity: PersistentEntityDefinition): string;
|
|
51
|
-
/**
|
|
52
|
-
* Genera código de schemas Zod desde EntityDefinition
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
|
-
* const code = generateZodSchema(postEntity)
|
|
56
|
-
* // Genera createPostSchema, updatePostSchema, postParamsSchema, postQuerySchema
|
|
57
|
-
*/
|
|
58
|
-
declare function generateZodSchema(entity: PersistentEntityDefinition): string;
|
|
59
37
|
/**
|
|
60
38
|
* Genera interface TypeScript desde EntityDefinition
|
|
61
39
|
*
|
|
@@ -69,60 +47,19 @@ declare function generateZodSchema(entity: PersistentEntityDefinition): string;
|
|
|
69
47
|
* // }
|
|
70
48
|
*/
|
|
71
49
|
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
50
|
/**
|
|
78
51
|
* Genera interface TypeScript para entidades read-only
|
|
79
52
|
*/
|
|
80
53
|
declare function generateReadOnlyModel(entity: ComputedEntityDefinition | ExternalEntityDefinition | VirtualEntityDefinition): string;
|
|
81
|
-
/**
|
|
82
|
-
* Estructura de permiso generado para insertar en BD
|
|
83
|
-
*/
|
|
84
|
-
interface GeneratedPermission {
|
|
85
|
-
role: string;
|
|
86
|
-
action: string;
|
|
87
|
-
subject: string;
|
|
88
|
-
conditions: string | null;
|
|
89
|
-
fields: string | null;
|
|
90
|
-
inverted: boolean;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Genera permisos CASL desde EntityDefinition
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* const permissions = generateCaslPermissions(postEntity)
|
|
97
|
-
* // Genera array de permisos para insertar en rol_role_permissions
|
|
98
|
-
*/
|
|
99
|
-
declare function generateCaslPermissions(entity: PersistentEntityDefinition): GeneratedPermission[];
|
|
100
|
-
/**
|
|
101
|
-
* Genera código de seed para permisos CASL
|
|
102
|
-
*
|
|
103
|
-
* @example
|
|
104
|
-
* const code = generateCaslSeed([postEntity, pageEntity])
|
|
105
|
-
* // Genera código para insertar permisos en rol_role_permissions
|
|
106
|
-
*/
|
|
107
|
-
declare function generateCaslSeed(entities: EntityDefinition[]): string;
|
|
108
|
-
/**
|
|
109
|
-
* Obtiene el subject CASL de una entidad
|
|
110
|
-
*/
|
|
111
|
-
declare function getEntitySubject(entity: PersistentEntityDefinition): string;
|
|
112
54
|
/**
|
|
113
55
|
* Obtiene el nombre de una entidad en PascalCase singular
|
|
114
56
|
* 'cms_posts' → 'Post', 'rol_role_permissions' → 'RolePermission'
|
|
115
57
|
*/
|
|
116
58
|
declare function getEntityName(entity: EntityDefinition): string;
|
|
117
59
|
/**
|
|
118
|
-
*
|
|
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)
|
|
60
|
+
* Obtiene el subject CASL de una entidad
|
|
124
61
|
*/
|
|
125
|
-
declare function
|
|
62
|
+
declare function getEntitySubject(entity: PersistentEntityDefinition): string;
|
|
126
63
|
/**
|
|
127
64
|
* Genera archivo completo de modelos TypeScript para múltiples entidades
|
|
128
65
|
* Consolida todas las entidades de un módulo en un solo archivo
|
|
@@ -132,15 +69,6 @@ declare function generateSchemasFile(definitions: EntityDefinition[]): string;
|
|
|
132
69
|
* writeFileSync('users.models.ts', code)
|
|
133
70
|
*/
|
|
134
71
|
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;
|
|
144
72
|
|
|
145
73
|
/**
|
|
146
74
|
* @gzl10/nexus-sdk
|
|
@@ -412,6 +340,8 @@ interface BaseEntityDefinition {
|
|
|
412
340
|
fields: Record<string, FieldDefinition>;
|
|
413
341
|
/** Autorización CASL */
|
|
414
342
|
casl?: EntityCaslConfig;
|
|
343
|
+
/** Prefijo de ruta para montar (default: inferido de table) */
|
|
344
|
+
routePrefix?: string;
|
|
415
345
|
}
|
|
416
346
|
/**
|
|
417
347
|
* Entidad de colección - CRUD completo (users, posts, orders)
|
|
@@ -451,6 +381,8 @@ interface SingleEntityDefinition {
|
|
|
451
381
|
defaults?: Record<string, unknown>;
|
|
452
382
|
/** Autorización CASL */
|
|
453
383
|
casl?: EntityCaslConfig;
|
|
384
|
+
/** Prefijo de ruta para montar (default: inferido de key) */
|
|
385
|
+
routePrefix?: string;
|
|
454
386
|
}
|
|
455
387
|
/**
|
|
456
388
|
* Entidad de referencia - Catálogos con CRUD admin (countries, currencies)
|
|
@@ -502,6 +434,8 @@ interface ActionEntityDefinition {
|
|
|
502
434
|
handler?: (ctx: ModuleContext, input: unknown) => Promise<unknown>;
|
|
503
435
|
/** Autorización CASL */
|
|
504
436
|
casl?: EntityCaslConfig;
|
|
437
|
+
/** Prefijo de ruta para montar */
|
|
438
|
+
routePrefix?: string;
|
|
505
439
|
}
|
|
506
440
|
/**
|
|
507
441
|
* Entidad externa - Datos de APIs externas (stripe_customers, github_repos)
|
|
@@ -533,6 +467,8 @@ interface ExternalEntityDefinition {
|
|
|
533
467
|
};
|
|
534
468
|
/** Autorización CASL */
|
|
535
469
|
casl?: EntityCaslConfig;
|
|
470
|
+
/** Prefijo de ruta para montar */
|
|
471
|
+
routePrefix?: string;
|
|
536
472
|
}
|
|
537
473
|
/**
|
|
538
474
|
* Entidad virtual - Orquestación de múltiples fuentes (unified_customers)
|
|
@@ -552,6 +488,8 @@ interface VirtualEntityDefinition {
|
|
|
552
488
|
resolver?: (sources: Record<string, unknown[]>, ctx: ModuleContext) => unknown[] | Promise<unknown[]>;
|
|
553
489
|
/** Autorización CASL */
|
|
554
490
|
casl?: EntityCaslConfig;
|
|
491
|
+
/** Prefijo de ruta para montar */
|
|
492
|
+
routePrefix?: string;
|
|
555
493
|
}
|
|
556
494
|
/**
|
|
557
495
|
* Entidad computed - KPIs, estadísticas, métricas calculadas
|
|
@@ -574,6 +512,8 @@ interface ComputedEntityDefinition {
|
|
|
574
512
|
};
|
|
575
513
|
/** Autorización CASL */
|
|
576
514
|
casl?: EntityCaslConfig;
|
|
515
|
+
/** Prefijo de ruta para montar */
|
|
516
|
+
routePrefix?: string;
|
|
577
517
|
}
|
|
578
518
|
/**
|
|
579
519
|
* Entidad view - Vista optimizada para lectura (projections, denormalizaciones)
|
|
@@ -595,6 +535,8 @@ interface ViewEntityDefinition {
|
|
|
595
535
|
query?: string | ((db: Knex) => Knex.QueryBuilder);
|
|
596
536
|
/** Autorización CASL */
|
|
597
537
|
casl?: EntityCaslConfig;
|
|
538
|
+
/** Prefijo de ruta para montar */
|
|
539
|
+
routePrefix?: string;
|
|
598
540
|
}
|
|
599
541
|
/**
|
|
600
542
|
* Entidad config - Configuración por módulo/tenant
|
|
@@ -746,6 +688,9 @@ interface ModuleContext {
|
|
|
746
688
|
UnauthorizedError: new (message?: string) => Error;
|
|
747
689
|
ForbiddenError: new (message?: string) => Error;
|
|
748
690
|
ConflictError: new (message?: string) => Error;
|
|
691
|
+
ValidationError: new (message?: string, errors?: unknown[]) => Error & {
|
|
692
|
+
errors: unknown[];
|
|
693
|
+
};
|
|
749
694
|
};
|
|
750
695
|
abilities: ModuleAbilities;
|
|
751
696
|
/** Sistema de eventos (EventEmitter2 compatible, permite implementaciones tipadas) */
|
|
@@ -826,4 +771,4 @@ interface PluginManifest {
|
|
|
826
771
|
modules: ModuleManifest[];
|
|
827
772
|
}
|
|
828
773
|
|
|
829
|
-
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 EntityType, 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
|
|
774
|
+
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 EntityType, 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 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, generateModel, generateModelsFile, generateReadOnlyModel, getEntityName, getEntitySubject, hasTable, isPersistentEntity, isSingletonEntity };
|
package/dist/index.js
CHANGED
|
@@ -1,33 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GENERATED_DIR,
|
|
3
|
-
generateCaslPermissions,
|
|
4
|
-
generateCaslSeed,
|
|
5
|
-
generateMigration,
|
|
6
|
-
generateMigrationsFile,
|
|
7
3
|
generateModel,
|
|
8
4
|
generateModelsFile,
|
|
9
5
|
generateReadOnlyModel,
|
|
10
|
-
generateReadOnlySchema,
|
|
11
|
-
generateSchemasFile,
|
|
12
|
-
generateZodSchema,
|
|
13
6
|
getEntityName,
|
|
14
7
|
getEntitySubject,
|
|
15
8
|
hasTable,
|
|
16
9
|
isPersistentEntity,
|
|
17
10
|
isSingletonEntity
|
|
18
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-UZT5CTOT.js";
|
|
19
12
|
export {
|
|
20
13
|
GENERATED_DIR,
|
|
21
|
-
generateCaslPermissions,
|
|
22
|
-
generateCaslSeed,
|
|
23
|
-
generateMigration,
|
|
24
|
-
generateMigrationsFile,
|
|
25
14
|
generateModel,
|
|
26
15
|
generateModelsFile,
|
|
27
16
|
generateReadOnlyModel,
|
|
28
|
-
generateReadOnlySchema,
|
|
29
|
-
generateSchemasFile,
|
|
30
|
-
generateZodSchema,
|
|
31
17
|
getEntityName,
|
|
32
18
|
getEntitySubject,
|
|
33
19
|
hasTable,
|
package/package.json
CHANGED
package/dist/chunk-XEPNN6IG.js
DELETED
|
@@ -1,806 +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 generateMigration(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 indexes = "indexes" in entity ? entity.indexes : void 0;
|
|
18
|
-
const lines = [
|
|
19
|
-
`import type { ModuleContext, Knex } from '@gzl10/nexus-sdk'`,
|
|
20
|
-
``,
|
|
21
|
-
`export async function migrate(ctx: ModuleContext): Promise<void> {`,
|
|
22
|
-
` const { db, logger, helpers } = ctx`
|
|
23
|
-
];
|
|
24
|
-
if (timestamps) {
|
|
25
|
-
lines.push(` const { addTimestamps } = helpers`);
|
|
26
|
-
}
|
|
27
|
-
if (audit) {
|
|
28
|
-
lines.push(` const { addAuditFieldsIfMissing } = helpers`);
|
|
29
|
-
}
|
|
30
|
-
lines.push(``);
|
|
31
|
-
lines.push(` if (!(await db.schema.hasTable('${table}'))) {`);
|
|
32
|
-
lines.push(` await db.schema.createTable('${table}', (table: Knex.CreateTableBuilder) => {`);
|
|
33
|
-
for (const [name, field] of Object.entries(fields)) {
|
|
34
|
-
const columnCode = generateColumnCode(name, field);
|
|
35
|
-
lines.push(` ${columnCode}`);
|
|
36
|
-
}
|
|
37
|
-
if (timestamps) {
|
|
38
|
-
lines.push(` addTimestamps(table, db)`);
|
|
39
|
-
}
|
|
40
|
-
if (indexes?.length) {
|
|
41
|
-
lines.push(``);
|
|
42
|
-
for (const idx of indexes) {
|
|
43
|
-
if (idx.unique) {
|
|
44
|
-
lines.push(` table.unique([${idx.columns.map((c) => `'${c}'`).join(", ")}])`);
|
|
45
|
-
} else {
|
|
46
|
-
lines.push(` table.index([${idx.columns.map((c) => `'${c}'`).join(", ")}])`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
lines.push(` })`);
|
|
51
|
-
lines.push(` logger.info('Created table: ${table}')`);
|
|
52
|
-
lines.push(` }`);
|
|
53
|
-
if (audit) {
|
|
54
|
-
lines.push(``);
|
|
55
|
-
lines.push(` await addAuditFieldsIfMissing(db, '${table}')`);
|
|
56
|
-
}
|
|
57
|
-
lines.push(`}`);
|
|
58
|
-
lines.push(``);
|
|
59
|
-
return lines.join("\n");
|
|
60
|
-
}
|
|
61
|
-
function generateColumnCode(name, field) {
|
|
62
|
-
const { db, relation } = field;
|
|
63
|
-
let code = "";
|
|
64
|
-
switch (db.type) {
|
|
65
|
-
case "string":
|
|
66
|
-
code = db.size ? `table.string('${name}', ${db.size})` : `table.string('${name}')`;
|
|
67
|
-
break;
|
|
68
|
-
case "text":
|
|
69
|
-
code = `table.text('${name}')`;
|
|
70
|
-
break;
|
|
71
|
-
case "integer":
|
|
72
|
-
code = `table.integer('${name}')`;
|
|
73
|
-
break;
|
|
74
|
-
case "decimal":
|
|
75
|
-
if (db.precision) {
|
|
76
|
-
code = `table.decimal('${name}', ${db.precision[0]}, ${db.precision[1]})`;
|
|
77
|
-
} else {
|
|
78
|
-
code = `table.decimal('${name}')`;
|
|
79
|
-
}
|
|
80
|
-
break;
|
|
81
|
-
case "boolean":
|
|
82
|
-
code = `table.boolean('${name}')`;
|
|
83
|
-
break;
|
|
84
|
-
case "date":
|
|
85
|
-
code = `table.date('${name}')`;
|
|
86
|
-
break;
|
|
87
|
-
case "datetime":
|
|
88
|
-
code = `table.timestamp('${name}')`;
|
|
89
|
-
break;
|
|
90
|
-
case "json":
|
|
91
|
-
code = `table.json('${name}')`;
|
|
92
|
-
break;
|
|
93
|
-
case "uuid":
|
|
94
|
-
code = `table.uuid('${name}')`;
|
|
95
|
-
break;
|
|
96
|
-
default:
|
|
97
|
-
code = `table.string('${name}')`;
|
|
98
|
-
}
|
|
99
|
-
if (name === "id") {
|
|
100
|
-
code += `.primary()`;
|
|
101
|
-
}
|
|
102
|
-
if (db.nullable) {
|
|
103
|
-
code += `.nullable()`;
|
|
104
|
-
} else {
|
|
105
|
-
code += `.notNullable()`;
|
|
106
|
-
}
|
|
107
|
-
if (db.unique) {
|
|
108
|
-
code += `.unique()`;
|
|
109
|
-
}
|
|
110
|
-
if (db.default !== void 0) {
|
|
111
|
-
if (typeof db.default === "string") {
|
|
112
|
-
code += `.defaultTo('${db.default}')`;
|
|
113
|
-
} else {
|
|
114
|
-
code += `.defaultTo(${JSON.stringify(db.default)})`;
|
|
115
|
-
}
|
|
116
|
-
} else if (db.defaultFn === "now") {
|
|
117
|
-
code += `.defaultTo(db.fn.now())`;
|
|
118
|
-
} else if (db.defaultFn === "uuid") {
|
|
119
|
-
code += `.defaultTo(db.raw('gen_random_uuid()'))`;
|
|
120
|
-
}
|
|
121
|
-
if (relation) {
|
|
122
|
-
const col = relation.column ?? "id";
|
|
123
|
-
code += `.references('${col}').inTable('${relation.table}')`;
|
|
124
|
-
if (relation.onDelete) {
|
|
125
|
-
code += `.onDelete('${relation.onDelete}')`;
|
|
126
|
-
}
|
|
127
|
-
if (relation.onUpdate) {
|
|
128
|
-
code += `.onUpdate('${relation.onUpdate}')`;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (db.index && !db.unique && name !== "id") {
|
|
132
|
-
}
|
|
133
|
-
return code;
|
|
134
|
-
}
|
|
135
|
-
function generateZodSchema(entity) {
|
|
136
|
-
const { table, fields } = entity;
|
|
137
|
-
const entityName = tableToEntityName(table);
|
|
138
|
-
const lines = [
|
|
139
|
-
`import { z } from 'zod'`,
|
|
140
|
-
``
|
|
141
|
-
];
|
|
142
|
-
lines.push(`// === CREATE ===`);
|
|
143
|
-
lines.push(`export const create${entityName}Schema = z.object({`);
|
|
144
|
-
for (const [name, field] of Object.entries(fields)) {
|
|
145
|
-
if (name === "id" || field.meta?.auditField) continue;
|
|
146
|
-
const zodCode = generateZodFieldCode(field, "create");
|
|
147
|
-
if (zodCode) {
|
|
148
|
-
lines.push(` ${name}: ${zodCode},`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
lines.push(`})`);
|
|
152
|
-
lines.push(``);
|
|
153
|
-
lines.push(`// === UPDATE ===`);
|
|
154
|
-
lines.push(`export const update${entityName}Schema = z.object({`);
|
|
155
|
-
for (const [name, field] of Object.entries(fields)) {
|
|
156
|
-
if (name === "id" || field.meta?.auditField) continue;
|
|
157
|
-
const zodCode = generateZodFieldCode(field, "update");
|
|
158
|
-
if (zodCode) {
|
|
159
|
-
lines.push(` ${name}: ${zodCode},`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
lines.push(`})`);
|
|
163
|
-
lines.push(``);
|
|
164
|
-
lines.push(`// === PARAMS ===`);
|
|
165
|
-
lines.push(`export const ${entityName.toLowerCase()}ParamsSchema = z.object({`);
|
|
166
|
-
lines.push(` id: z.string(),`);
|
|
167
|
-
lines.push(`})`);
|
|
168
|
-
lines.push(``);
|
|
169
|
-
lines.push(`// === QUERY ===`);
|
|
170
|
-
lines.push(`export const ${entityName.toLowerCase()}QuerySchema = z.object({`);
|
|
171
|
-
lines.push(` page: z.coerce.number().int().min(1).default(1),`);
|
|
172
|
-
lines.push(` limit: z.coerce.number().int().min(1).max(100).default(20),`);
|
|
173
|
-
for (const [name, field] of Object.entries(fields)) {
|
|
174
|
-
if (field.meta?.searchable) {
|
|
175
|
-
const zodType = dbTypeToZodType(field.db.type);
|
|
176
|
-
lines.push(` ${name}: ${zodType}.optional(),`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
lines.push(`})`);
|
|
180
|
-
lines.push(``);
|
|
181
|
-
lines.push(`// === INFERRED TYPES ===`);
|
|
182
|
-
lines.push(`export type Create${entityName}Input = z.infer<typeof create${entityName}Schema>`);
|
|
183
|
-
lines.push(`export type Update${entityName}Input = z.infer<typeof update${entityName}Schema>`);
|
|
184
|
-
lines.push(`export type ${entityName}Params = z.infer<typeof ${entityName.toLowerCase()}ParamsSchema>`);
|
|
185
|
-
lines.push(`export type ${entityName}Query = z.infer<typeof ${entityName.toLowerCase()}QuerySchema>`);
|
|
186
|
-
lines.push(``);
|
|
187
|
-
return lines.join("\n");
|
|
188
|
-
}
|
|
189
|
-
function generateZodFieldCode(field, mode) {
|
|
190
|
-
const { db, validation } = field;
|
|
191
|
-
let code = dbTypeToZodType(db.type);
|
|
192
|
-
if (validation) {
|
|
193
|
-
if (validation.format === "email") {
|
|
194
|
-
code += `.email()`;
|
|
195
|
-
} else if (validation.format === "url") {
|
|
196
|
-
code += `.url()`;
|
|
197
|
-
} else if (validation.format === "uuid") {
|
|
198
|
-
code += `.uuid()`;
|
|
199
|
-
}
|
|
200
|
-
if (validation.min !== void 0) {
|
|
201
|
-
if (db.type === "string" || db.type === "text") {
|
|
202
|
-
code += `.min(${validation.min})`;
|
|
203
|
-
} else if (db.type === "integer" || db.type === "decimal") {
|
|
204
|
-
code += `.min(${validation.min})`;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (validation.max !== void 0) {
|
|
208
|
-
if (db.type === "string" || db.type === "text") {
|
|
209
|
-
code += `.max(${validation.max})`;
|
|
210
|
-
} else if (db.type === "integer" || db.type === "decimal") {
|
|
211
|
-
code += `.max(${validation.max})`;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (validation.pattern) {
|
|
215
|
-
code += `.regex(/${validation.pattern}/)`;
|
|
216
|
-
}
|
|
217
|
-
if (validation.enum?.length) {
|
|
218
|
-
code = `z.enum([${validation.enum.map((v) => `'${v}'`).join(", ")}])`;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
if (db.nullable) {
|
|
222
|
-
code += `.nullable()`;
|
|
223
|
-
}
|
|
224
|
-
if (mode === "update") {
|
|
225
|
-
code += `.optional()`;
|
|
226
|
-
} else if (mode === "create" && !validation?.required && db.default !== void 0) {
|
|
227
|
-
code += `.optional()`;
|
|
228
|
-
}
|
|
229
|
-
return code;
|
|
230
|
-
}
|
|
231
|
-
function dbTypeToZodType(dbType) {
|
|
232
|
-
switch (dbType) {
|
|
233
|
-
case "string":
|
|
234
|
-
case "text":
|
|
235
|
-
case "uuid":
|
|
236
|
-
return "z.string()";
|
|
237
|
-
case "integer":
|
|
238
|
-
return "z.number().int()";
|
|
239
|
-
case "decimal":
|
|
240
|
-
return "z.number()";
|
|
241
|
-
case "boolean":
|
|
242
|
-
return "z.boolean()";
|
|
243
|
-
case "date":
|
|
244
|
-
case "datetime":
|
|
245
|
-
return "z.string().datetime()";
|
|
246
|
-
case "json":
|
|
247
|
-
return "z.record(z.unknown())";
|
|
248
|
-
default:
|
|
249
|
-
return "z.string()";
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
function generateModel(entity) {
|
|
253
|
-
const { table, fields } = entity;
|
|
254
|
-
const timestamps = "timestamps" in entity ? entity.timestamps : false;
|
|
255
|
-
const audit = "audit" in entity ? entity.audit : false;
|
|
256
|
-
const entityName = tableToEntityName(table);
|
|
257
|
-
const lines = [
|
|
258
|
-
`/**`,
|
|
259
|
-
` * ${entity.label}`,
|
|
260
|
-
` * Generated from EntityDefinition`,
|
|
261
|
-
` */`,
|
|
262
|
-
`export interface ${entityName} {`
|
|
263
|
-
];
|
|
264
|
-
for (const [name, field] of Object.entries(fields)) {
|
|
265
|
-
const tsType = dbTypeToTsType(field.db);
|
|
266
|
-
const optional = field.db.nullable ? "?" : "";
|
|
267
|
-
lines.push(` ${name}${optional}: ${tsType}`);
|
|
268
|
-
}
|
|
269
|
-
if (timestamps) {
|
|
270
|
-
lines.push(` created_at: Date`);
|
|
271
|
-
lines.push(` updated_at: Date`);
|
|
272
|
-
}
|
|
273
|
-
if (audit) {
|
|
274
|
-
lines.push(` created_by: string | null`);
|
|
275
|
-
lines.push(` updated_by: string | null`);
|
|
276
|
-
}
|
|
277
|
-
lines.push(`}`);
|
|
278
|
-
lines.push(``);
|
|
279
|
-
return lines.join("\n");
|
|
280
|
-
}
|
|
281
|
-
function dbTypeToTsType(db) {
|
|
282
|
-
switch (db.type) {
|
|
283
|
-
case "string":
|
|
284
|
-
case "text":
|
|
285
|
-
case "uuid":
|
|
286
|
-
return "string";
|
|
287
|
-
case "integer":
|
|
288
|
-
case "decimal":
|
|
289
|
-
return "number";
|
|
290
|
-
case "boolean":
|
|
291
|
-
return "boolean";
|
|
292
|
-
case "date":
|
|
293
|
-
case "datetime":
|
|
294
|
-
return "Date";
|
|
295
|
-
case "json":
|
|
296
|
-
return "Record<string, unknown>";
|
|
297
|
-
default:
|
|
298
|
-
return "unknown";
|
|
299
|
-
}
|
|
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
|
-
}
|
|
309
|
-
function tableToEntityName(table) {
|
|
310
|
-
const withoutPrefix = table.replace(/^[a-z]{2,4}_/, "");
|
|
311
|
-
return toPascalCase(toSingular(withoutPrefix));
|
|
312
|
-
}
|
|
313
|
-
function tableToSubject(table) {
|
|
314
|
-
const match = table.match(/^([a-z]{2,4})_(.+)$/);
|
|
315
|
-
if (!match) return tableToEntityName(table);
|
|
316
|
-
const [, prefix, rest] = match;
|
|
317
|
-
const prefixPascal = prefix.charAt(0).toUpperCase() + prefix.slice(1);
|
|
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");
|
|
376
|
-
}
|
|
377
|
-
function getFieldsForRole(entity, role, action) {
|
|
378
|
-
const { fields, casl } = entity;
|
|
379
|
-
const isReadAction = action === "read";
|
|
380
|
-
const isWriteAction = action === "create" || action === "update";
|
|
381
|
-
if (action === "manage") return null;
|
|
382
|
-
const allowedFields = [];
|
|
383
|
-
const sensitiveFields = casl?.sensitiveFields ?? [];
|
|
384
|
-
for (const [fieldName, field] of Object.entries(fields)) {
|
|
385
|
-
const fieldCasl = field.meta?.casl;
|
|
386
|
-
if (sensitiveFields.includes(fieldName) && role !== "ADMIN") {
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
if (isReadAction) {
|
|
390
|
-
if (fieldCasl?.readRoles !== void 0) {
|
|
391
|
-
if (fieldCasl.readRoles.length === 0 || fieldCasl.readRoles.includes(role)) {
|
|
392
|
-
allowedFields.push(fieldName);
|
|
393
|
-
}
|
|
394
|
-
} else {
|
|
395
|
-
allowedFields.push(fieldName);
|
|
396
|
-
}
|
|
397
|
-
} else if (isWriteAction) {
|
|
398
|
-
if (fieldCasl?.writeRoles !== void 0) {
|
|
399
|
-
if (fieldCasl.writeRoles.length === 0 || fieldCasl.writeRoles.includes(role)) {
|
|
400
|
-
allowedFields.push(fieldName);
|
|
401
|
-
}
|
|
402
|
-
} else {
|
|
403
|
-
allowedFields.push(fieldName);
|
|
404
|
-
}
|
|
405
|
-
} else {
|
|
406
|
-
return null;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
const allFieldNames = Object.keys(fields).filter((f) => !sensitiveFields.includes(f) || role === "ADMIN");
|
|
410
|
-
if (allowedFields.length === allFieldNames.length) {
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
return allowedFields.length > 0 ? allowedFields : null;
|
|
414
|
-
}
|
|
415
|
-
function generateCaslPermissions(entity) {
|
|
416
|
-
const { table, fields, casl } = entity;
|
|
417
|
-
if (!casl) return [];
|
|
418
|
-
const subject = casl.subject ?? tableToSubject(table);
|
|
419
|
-
const permissions = [];
|
|
420
|
-
for (const [role, config] of Object.entries(casl.permissions ?? {})) {
|
|
421
|
-
for (const action of config.actions) {
|
|
422
|
-
let conditions = config.conditions ?? null;
|
|
423
|
-
if (casl.ownership && (action === "update" || action === "delete") && !conditions) {
|
|
424
|
-
const userField = casl.ownership.userField ?? "id";
|
|
425
|
-
conditions = { [casl.ownership.field]: `\${user.${userField}}` };
|
|
426
|
-
}
|
|
427
|
-
let permFields = config.fields ?? null;
|
|
428
|
-
if (!permFields) {
|
|
429
|
-
permFields = getFieldsForRole(entity, role, action);
|
|
430
|
-
}
|
|
431
|
-
permissions.push({
|
|
432
|
-
role,
|
|
433
|
-
action,
|
|
434
|
-
subject,
|
|
435
|
-
conditions: conditions ? JSON.stringify(conditions) : null,
|
|
436
|
-
fields: permFields ? JSON.stringify(permFields) : null,
|
|
437
|
-
inverted: config.inverted ?? false
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return permissions;
|
|
442
|
-
}
|
|
443
|
-
function generateCaslSeed(entities) {
|
|
444
|
-
const allPermissions = [];
|
|
445
|
-
for (const entity of entities) {
|
|
446
|
-
if (isPersistentEntity(entity)) {
|
|
447
|
-
allPermissions.push(...generateCaslPermissions(entity));
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
if (allPermissions.length === 0) {
|
|
451
|
-
return "// No CASL permissions defined in entities\n";
|
|
452
|
-
}
|
|
453
|
-
const lines = [
|
|
454
|
-
`import type { ModuleContext } from '@gzl10/nexus-sdk'`,
|
|
455
|
-
``,
|
|
456
|
-
`/**`,
|
|
457
|
-
` * Seed de permisos CASL generado desde EntityDefinition`,
|
|
458
|
-
` */`,
|
|
459
|
-
`export async function seedPermissions(ctx: ModuleContext): Promise<void> {`,
|
|
460
|
-
` const { db, generateId, logger } = ctx`,
|
|
461
|
-
``,
|
|
462
|
-
` // Obtener IDs de roles`,
|
|
463
|
-
` const roles = await db('rol_roles').select('id', 'name')`,
|
|
464
|
-
` const roleMap = Object.fromEntries(roles.map((r: { id: string; name: string }) => [r.name, r.id]))`,
|
|
465
|
-
``,
|
|
466
|
-
` const permissions = [`
|
|
467
|
-
];
|
|
468
|
-
for (const perm of allPermissions) {
|
|
469
|
-
lines.push(` {`);
|
|
470
|
-
lines.push(` role: '${perm.role}',`);
|
|
471
|
-
lines.push(` action: '${perm.action}',`);
|
|
472
|
-
lines.push(` subject: '${perm.subject}',`);
|
|
473
|
-
lines.push(` conditions: ${perm.conditions ? `'${perm.conditions}'` : "null"},`);
|
|
474
|
-
lines.push(` fields: ${perm.fields ? `'${perm.fields}'` : "null"},`);
|
|
475
|
-
lines.push(` inverted: ${perm.inverted}`);
|
|
476
|
-
lines.push(` },`);
|
|
477
|
-
}
|
|
478
|
-
lines.push(` ]`);
|
|
479
|
-
lines.push(``);
|
|
480
|
-
lines.push(` for (const perm of permissions) {`);
|
|
481
|
-
lines.push(` const roleId = roleMap[perm.role]`);
|
|
482
|
-
lines.push(` if (!roleId) {`);
|
|
483
|
-
lines.push(` logger.warn({ role: perm.role }, 'Role not found, skipping permission')`);
|
|
484
|
-
lines.push(` continue`);
|
|
485
|
-
lines.push(` }`);
|
|
486
|
-
lines.push(``);
|
|
487
|
-
lines.push(` // Check if permission exists`);
|
|
488
|
-
lines.push(` const existing = await db('rol_role_permissions')`);
|
|
489
|
-
lines.push(` .where({ role_id: roleId, action: perm.action, subject: perm.subject })`);
|
|
490
|
-
lines.push(` .first()`);
|
|
491
|
-
lines.push(``);
|
|
492
|
-
lines.push(` if (!existing) {`);
|
|
493
|
-
lines.push(` await db('rol_role_permissions').insert({`);
|
|
494
|
-
lines.push(` id: generateId(),`);
|
|
495
|
-
lines.push(` role_id: roleId,`);
|
|
496
|
-
lines.push(` action: perm.action,`);
|
|
497
|
-
lines.push(` subject: perm.subject,`);
|
|
498
|
-
lines.push(` conditions: perm.conditions,`);
|
|
499
|
-
lines.push(` fields: perm.fields,`);
|
|
500
|
-
lines.push(` inverted: perm.inverted`);
|
|
501
|
-
lines.push(` })`);
|
|
502
|
-
lines.push(` }`);
|
|
503
|
-
lines.push(` }`);
|
|
504
|
-
lines.push(``);
|
|
505
|
-
lines.push(` logger.info('Seeded CASL permissions from EntityDefinitions')`);
|
|
506
|
-
lines.push(`}`);
|
|
507
|
-
lines.push(``);
|
|
508
|
-
return lines.join("\n");
|
|
509
|
-
}
|
|
510
|
-
function getEntitySubject(entity) {
|
|
511
|
-
return entity.casl?.subject ?? tableToSubject(entity.table);
|
|
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
|
-
}
|
|
788
|
-
|
|
789
|
-
export {
|
|
790
|
-
GENERATED_DIR,
|
|
791
|
-
isPersistentEntity,
|
|
792
|
-
isSingletonEntity,
|
|
793
|
-
hasTable,
|
|
794
|
-
generateMigration,
|
|
795
|
-
generateZodSchema,
|
|
796
|
-
generateModel,
|
|
797
|
-
generateReadOnlySchema,
|
|
798
|
-
generateReadOnlyModel,
|
|
799
|
-
generateCaslPermissions,
|
|
800
|
-
generateCaslSeed,
|
|
801
|
-
getEntitySubject,
|
|
802
|
-
getEntityName,
|
|
803
|
-
generateSchemasFile,
|
|
804
|
-
generateModelsFile,
|
|
805
|
-
generateMigrationsFile
|
|
806
|
-
};
|