mutano 3.0.0 → 3.0.2
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 +110 -734
- package/dist/constants.d.ts +6 -0
- package/dist/database/connection.d.ts +25 -0
- package/dist/database/prisma.d.ts +20 -0
- package/dist/generators/content-generator.d.ts +12 -0
- package/dist/generators/type-generator.d.ts +9 -0
- package/dist/main.d.ts +14 -99
- package/dist/main.js +140 -914
- package/dist/types/index.d.ts +107 -0
- package/dist/types/mappings.d.ts +91 -0
- package/dist/utils/filters.d.ts +23 -0
- package/dist/utils/magic-comments.d.ts +33 -0
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1,933 +1,158 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
const enumDeclarations = {};
|
|
4
|
-
import {
|
|
5
|
-
createPrismaSchemaBuilder
|
|
6
|
-
} from "@mrleebo/prisma-ast";
|
|
1
|
+
import * as path from "node:path";
|
|
7
2
|
import camelCase from "camelcase";
|
|
8
|
-
import fs from "fs-extra";
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
31
|
-
const extractTSExpression = (comment) => extractTypeExpression(comment, "@ts(");
|
|
32
|
-
const extractKyselyExpression = (comment) => extractTypeExpression(comment, "@kysely(");
|
|
33
|
-
const extractZodExpression = (comment) => extractTypeExpression(comment, "@zod(");
|
|
34
|
-
const prismaValidTypes = [
|
|
35
|
-
"BigInt",
|
|
36
|
-
"Boolean",
|
|
37
|
-
"Bytes",
|
|
38
|
-
"DateTime",
|
|
39
|
-
"Decimal",
|
|
40
|
-
"Float",
|
|
41
|
-
"Int",
|
|
42
|
-
"Json",
|
|
43
|
-
"String",
|
|
44
|
-
"Enum"
|
|
45
|
-
];
|
|
46
|
-
const dateTypes = {
|
|
47
|
-
mysql: ["date", "datetime", "timestamp"],
|
|
48
|
-
postgres: [
|
|
49
|
-
"date",
|
|
50
|
-
"timestamp",
|
|
51
|
-
"timestamptz",
|
|
52
|
-
"timestamp without time zone",
|
|
53
|
-
"timestamp with time zone"
|
|
54
|
-
],
|
|
55
|
-
sqlite: ["datetime"],
|
|
56
|
-
prisma: ["DateTime"]
|
|
57
|
-
};
|
|
58
|
-
const stringTypes = {
|
|
59
|
-
mysql: [
|
|
60
|
-
"tinytext",
|
|
61
|
-
"text",
|
|
62
|
-
"mediumtext",
|
|
63
|
-
"longtext",
|
|
64
|
-
"json",
|
|
65
|
-
"time",
|
|
66
|
-
"year",
|
|
67
|
-
"char",
|
|
68
|
-
"varchar"
|
|
69
|
-
],
|
|
70
|
-
postgres: [
|
|
71
|
-
"text",
|
|
72
|
-
"character varying",
|
|
73
|
-
"varchar",
|
|
74
|
-
"char",
|
|
75
|
-
"character",
|
|
76
|
-
"json",
|
|
77
|
-
"jsonb",
|
|
78
|
-
"uuid",
|
|
79
|
-
"time",
|
|
80
|
-
"timetz",
|
|
81
|
-
"interval",
|
|
82
|
-
"name",
|
|
83
|
-
"citext"
|
|
84
|
-
],
|
|
85
|
-
sqlite: [
|
|
86
|
-
"text",
|
|
87
|
-
"character",
|
|
88
|
-
"varchar",
|
|
89
|
-
"varying character",
|
|
90
|
-
"nchar",
|
|
91
|
-
"native character",
|
|
92
|
-
"nvarchar",
|
|
93
|
-
"clob",
|
|
94
|
-
"json"
|
|
95
|
-
],
|
|
96
|
-
prisma: ["String", "Bytes", "Json"]
|
|
97
|
-
};
|
|
98
|
-
const bigIntTypes = {
|
|
99
|
-
mysql: ["bigint"],
|
|
100
|
-
postgres: ["bigint"],
|
|
101
|
-
sqlite: ["bigint"],
|
|
102
|
-
prisma: ["BigInt"]
|
|
103
|
-
};
|
|
104
|
-
const numberTypes = {
|
|
105
|
-
mysql: ["smallint", "mediumint", "int", "float", "double"],
|
|
106
|
-
postgres: [
|
|
107
|
-
"smallint",
|
|
108
|
-
"integer",
|
|
109
|
-
"real",
|
|
110
|
-
"double precision",
|
|
111
|
-
"serial",
|
|
112
|
-
"bigserial"
|
|
113
|
-
],
|
|
114
|
-
sqlite: [
|
|
115
|
-
"int",
|
|
116
|
-
"integer",
|
|
117
|
-
"tinyint",
|
|
118
|
-
"smallint",
|
|
119
|
-
"mediumint",
|
|
120
|
-
"unsigned big int",
|
|
121
|
-
"int2",
|
|
122
|
-
"int8",
|
|
123
|
-
"real",
|
|
124
|
-
"double",
|
|
125
|
-
"double precision",
|
|
126
|
-
"float"
|
|
127
|
-
],
|
|
128
|
-
prisma: ["Int", "Float"]
|
|
129
|
-
};
|
|
130
|
-
const decimalTypes = {
|
|
131
|
-
mysql: ["decimal"],
|
|
132
|
-
postgres: ["decimal", "numeric"],
|
|
133
|
-
sqlite: ["numeric", "decimal"],
|
|
134
|
-
prisma: ["Decimal"]
|
|
135
|
-
};
|
|
136
|
-
const booleanTypes = {
|
|
137
|
-
mysql: ["tinyint"],
|
|
138
|
-
postgres: ["boolean", "bool"],
|
|
139
|
-
sqlite: ["boolean"],
|
|
140
|
-
prisma: ["Boolean"]
|
|
141
|
-
};
|
|
142
|
-
const enumTypes = {
|
|
143
|
-
mysql: ["enum"],
|
|
144
|
-
postgres: ["USER-DEFINED"],
|
|
145
|
-
sqlite: [],
|
|
146
|
-
// SQLite doesn't have native enum types
|
|
147
|
-
prisma: ["Enum"]
|
|
148
|
-
};
|
|
149
|
-
const enumRegex = /enum\(([^)]+)\)/;
|
|
150
|
-
function getType(op, desc, config, destination, tableName) {
|
|
151
|
-
const schemaType = config.origin.type;
|
|
152
|
-
const { Default, Extra, Null, Type, Comment, EnumOptions } = desc;
|
|
153
|
-
const isZodDestination = destination.type === "zod";
|
|
154
|
-
const isTsDestination = destination.type === "ts";
|
|
155
|
-
const isKyselyDestination = destination.type === "kysely";
|
|
156
|
-
const isNullish = isZodDestination && destination.type === "zod" && destination.nullish === true;
|
|
157
|
-
const isTrim = isZodDestination && destination.type === "zod" && destination.useTrim === true && op !== "selectable";
|
|
158
|
-
const isUseDateType = isZodDestination && destination.type === "zod" && destination.useDateType === true;
|
|
159
|
-
const hasDefaultValue = Default !== null && op !== "selectable";
|
|
160
|
-
const isGenerated = ["DEFAULT_GENERATED", "auto_increment"].includes(Extra);
|
|
161
|
-
const isNull = Null === "YES";
|
|
162
|
-
if (isGenerated && !isNull && ["insertable", "updateable"].includes(op))
|
|
163
|
-
return;
|
|
164
|
-
const isRequiredString = destination.type === "zod" && destination.requiredString === true && op !== "selectable";
|
|
165
|
-
const type = schemaType === "mysql" ? Type.split("(")[0].split(" ")[0] : Type;
|
|
166
|
-
if (isTsDestination || isKyselyDestination) {
|
|
167
|
-
if (isKyselyDestination && config.magicComments) {
|
|
168
|
-
const kyselyOverrideType = extractKyselyExpression(Comment);
|
|
169
|
-
if (kyselyOverrideType) {
|
|
170
|
-
const shouldBeNullable2 = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
|
|
171
|
-
return shouldBeNullable2 ? kyselyOverrideType.includes("| null") ? kyselyOverrideType : `${kyselyOverrideType} | null` : kyselyOverrideType;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const tsOverrideType = config.magicComments ? extractTSExpression(Comment) : null;
|
|
175
|
-
const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
|
|
176
|
-
if (tsOverrideType) {
|
|
177
|
-
return shouldBeNullable ? tsOverrideType.includes("| null") ? tsOverrideType : `${tsOverrideType} | null` : tsOverrideType;
|
|
178
|
-
}
|
|
179
|
-
if (dateTypes[schemaType].includes(type)) {
|
|
180
|
-
return shouldBeNullable ? "Date | null" : "Date";
|
|
181
|
-
}
|
|
182
|
-
if (stringTypes[schemaType].includes(type)) {
|
|
183
|
-
return shouldBeNullable ? "string | null" : "string";
|
|
184
|
-
}
|
|
185
|
-
if (numberTypes[schemaType].includes(type)) {
|
|
186
|
-
return shouldBeNullable ? "number | null" : "number";
|
|
187
|
-
}
|
|
188
|
-
if (booleanTypes[schemaType].includes(type)) {
|
|
189
|
-
return shouldBeNullable ? "boolean | null" : "boolean";
|
|
190
|
-
}
|
|
191
|
-
if (bigIntTypes[schemaType].includes(type) || type === "BigInt") {
|
|
192
|
-
if (isKyselyDestination) {
|
|
193
|
-
return shouldBeNullable ? "BigInt | null" : "BigInt";
|
|
194
|
-
}
|
|
195
|
-
return shouldBeNullable ? "string | null" : "string";
|
|
196
|
-
}
|
|
197
|
-
if (decimalTypes[schemaType].includes(type) || type === "Decimal") {
|
|
198
|
-
if (isKyselyDestination) {
|
|
199
|
-
return shouldBeNullable ? "Decimal | null" : "Decimal";
|
|
200
|
-
}
|
|
201
|
-
return shouldBeNullable ? "string | null" : "string";
|
|
202
|
-
}
|
|
203
|
-
if (schemaType !== "sqlite" && enumTypes[schemaType].includes(type)) {
|
|
204
|
-
const enumType = destination.type === "ts" ? destination.enumType || "union" : "union";
|
|
205
|
-
let enumValues = [];
|
|
206
|
-
if (schemaType === "mysql") {
|
|
207
|
-
const matches = Type.match(enumRegex);
|
|
208
|
-
if (matches?.[1]) {
|
|
209
|
-
enumValues = matches[1].split(",").map((v) => v.trim()).sort();
|
|
210
|
-
}
|
|
211
|
-
} else if (EnumOptions && EnumOptions.length > 0) {
|
|
212
|
-
enumValues = EnumOptions.map((e) => `'${e}'`).sort();
|
|
213
|
-
}
|
|
214
|
-
if (enumValues.length === 0) {
|
|
215
|
-
return isNull ? "string | null" : "string";
|
|
216
|
-
}
|
|
217
|
-
if (enumType === "enum") {
|
|
218
|
-
const enumName = camelCase(`${desc.Field}_enum`, { pascalCase: true });
|
|
219
|
-
const enumDeclaration = `enum ${enumName} {
|
|
220
|
-
${enumValues.map((v) => {
|
|
221
|
-
const cleanName = v.replace(/['"]/g, "");
|
|
222
|
-
return `${cleanName} = ${v}`;
|
|
223
|
-
}).join(",\n ")}
|
|
224
|
-
}`;
|
|
225
|
-
if (tableName) {
|
|
226
|
-
if (!enumDeclarations[tableName]) {
|
|
227
|
-
enumDeclarations[tableName] = [];
|
|
228
|
-
}
|
|
229
|
-
if (!enumDeclarations[tableName].includes(enumDeclaration)) {
|
|
230
|
-
enumDeclarations[tableName].push(enumDeclaration);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return shouldBeNullable ? `${enumName} | null` : enumName;
|
|
234
|
-
}
|
|
235
|
-
const unionType = enumValues.join(" | ");
|
|
236
|
-
return shouldBeNullable ? `(${unionType}) | null` : unionType;
|
|
237
|
-
}
|
|
238
|
-
return "any";
|
|
239
|
-
}
|
|
240
|
-
const zDate = [
|
|
241
|
-
"z.union([z.number(), z.string(), z.date()]).pipe(z.coerce.date())"
|
|
242
|
-
];
|
|
243
|
-
const string = [isTrim ? "z.string().trim()" : "z.string()"];
|
|
244
|
-
const number = ["z.number()"];
|
|
245
|
-
const boolean = [
|
|
246
|
-
"z.union([z.number(),z.string(),z.boolean()]).pipe(z.coerce.boolean())"
|
|
247
|
-
];
|
|
248
|
-
const dateField = isUseDateType ? zDate : string;
|
|
249
|
-
const nullable = isNullish && op !== "selectable" ? "nullish()" : "nullable()";
|
|
250
|
-
const optional = "optional()";
|
|
251
|
-
const nonnegative = "nonnegative()";
|
|
252
|
-
const isUpdateableFormat = op === "updateable" && !isNull && !hasDefaultValue;
|
|
253
|
-
const min1 = "min(1)";
|
|
254
|
-
const zodOverrideType = config.magicComments ? extractZodExpression(Comment) : null;
|
|
255
|
-
let typeOverride = zodOverrideType;
|
|
256
|
-
if (!typeOverride && config.origin.overrideTypes) {
|
|
257
|
-
if (config.origin.type === "mysql") {
|
|
258
|
-
typeOverride = config.origin.overrideTypes[type] || null;
|
|
259
|
-
} else if (config.origin.type === "postgres") {
|
|
260
|
-
typeOverride = config.origin.overrideTypes[type] || null;
|
|
261
|
-
} else if (config.origin.type === "sqlite") {
|
|
262
|
-
typeOverride = config.origin.overrideTypes[type] || null;
|
|
263
|
-
} else if (config.origin.type === "prisma") {
|
|
264
|
-
typeOverride = config.origin.overrideTypes[type] || null;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
const generateDateLikeField = () => {
|
|
268
|
-
const field = typeOverride ? [typeOverride] : dateField;
|
|
269
|
-
if (isNull && !typeOverride) field.push(nullable);
|
|
270
|
-
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
271
|
-
field.push(optional);
|
|
272
|
-
if (hasDefaultValue && !isGenerated) field.push(`default('${Default}')`);
|
|
273
|
-
if (isUpdateableFormat) field.push(optional);
|
|
274
|
-
return field.join(".");
|
|
275
|
-
};
|
|
276
|
-
const generateStringLikeField = () => {
|
|
277
|
-
const field = typeOverride ? [typeOverride] : string;
|
|
278
|
-
if (isNull && !typeOverride) field.push(nullable);
|
|
279
|
-
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
280
|
-
field.push(optional);
|
|
281
|
-
else if (isRequiredString && !typeOverride) field.push(min1);
|
|
282
|
-
if (hasDefaultValue && !isGenerated) field.push(`default('${Default}')`);
|
|
283
|
-
if (isUpdateableFormat) field.push(optional);
|
|
284
|
-
return field.join(".");
|
|
285
|
-
};
|
|
286
|
-
const generateBooleanLikeField = () => {
|
|
287
|
-
const field = typeOverride ? [typeOverride] : boolean;
|
|
288
|
-
if (isNull && !typeOverride) field.push(nullable);
|
|
289
|
-
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
290
|
-
field.push(optional);
|
|
291
|
-
if (hasDefaultValue && !isGenerated) {
|
|
292
|
-
if (Default === "true" || Default === "false") {
|
|
293
|
-
field.push(`default(${Default})`);
|
|
294
|
-
} else {
|
|
295
|
-
field.push(`default(${Boolean(+Default)})`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (isUpdateableFormat) field.push(optional);
|
|
299
|
-
return field.join(".");
|
|
300
|
-
};
|
|
301
|
-
const generateNumberLikeField = () => {
|
|
302
|
-
const unsigned = Type.endsWith(" unsigned");
|
|
303
|
-
const field = typeOverride ? [typeOverride] : number;
|
|
304
|
-
if (unsigned && !typeOverride) field.push(nonnegative);
|
|
305
|
-
if (isNull && !typeOverride) field.push(nullable);
|
|
306
|
-
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
307
|
-
field.push(optional);
|
|
308
|
-
if (hasDefaultValue && !isGenerated) field.push(`default(${Default})`);
|
|
309
|
-
if (isUpdateableFormat) field.push(optional);
|
|
310
|
-
return field.join(".");
|
|
311
|
-
};
|
|
312
|
-
const generateEnumLikeField = () => {
|
|
313
|
-
let enumValues = [];
|
|
314
|
-
if (schemaType === "mysql") {
|
|
315
|
-
const matches = Type.match(enumRegex);
|
|
316
|
-
if (matches?.[1]) {
|
|
317
|
-
enumValues = matches[1].split(",").map((v) => v.trim()).sort();
|
|
318
|
-
}
|
|
319
|
-
} else if (EnumOptions && EnumOptions.length > 0) {
|
|
320
|
-
enumValues = [...EnumOptions].sort().map((e) => `'${e}'`);
|
|
321
|
-
}
|
|
322
|
-
const value = enumValues.join(",");
|
|
323
|
-
const field = [`z.enum([${value}])`];
|
|
324
|
-
if (isNull) field.push(nullable);
|
|
325
|
-
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
326
|
-
field.push(optional);
|
|
327
|
-
if (hasDefaultValue && !isGenerated) field.push(`default('${Default}')`);
|
|
328
|
-
if (isUpdateableFormat) field.push(optional);
|
|
329
|
-
return field.join(".");
|
|
330
|
-
};
|
|
331
|
-
if (dateTypes[schemaType].includes(type)) return generateDateLikeField();
|
|
332
|
-
if (stringTypes[schemaType].includes(type)) return generateStringLikeField();
|
|
333
|
-
if (numberTypes[schemaType].includes(type)) return generateNumberLikeField();
|
|
334
|
-
if (bigIntTypes[schemaType].includes(type) || type === "BigInt") {
|
|
335
|
-
if (isKyselyDestination) {
|
|
336
|
-
const isNull2 = Null === "YES";
|
|
337
|
-
const hasDefaultValue2 = Default !== null;
|
|
338
|
-
const isGenerated2 = Extra.toLowerCase().includes("auto_increment") || Extra.toLowerCase().includes("default_generated");
|
|
339
|
-
const shouldBeNullable = isNull2 || ["insertable", "updateable"].includes(op) && (hasDefaultValue2 || isGenerated2) || op === "updateable" && !isNull2 && !hasDefaultValue2;
|
|
340
|
-
return shouldBeNullable ? "BigInt | null" : "BigInt";
|
|
341
|
-
}
|
|
342
|
-
return generateStringLikeField();
|
|
343
|
-
}
|
|
344
|
-
if (decimalTypes[schemaType].includes(type) || type === "Decimal") {
|
|
345
|
-
if (isKyselyDestination) {
|
|
346
|
-
const isNull2 = Null === "YES";
|
|
347
|
-
const hasDefaultValue2 = Default !== null;
|
|
348
|
-
const isGenerated2 = Extra.toLowerCase().includes("auto_increment") || Extra.toLowerCase().includes("default_generated");
|
|
349
|
-
const shouldBeNullable = isNull2 || ["insertable", "updateable"].includes(op) && (hasDefaultValue2 || isGenerated2) || op === "updateable" && !isNull2 && !hasDefaultValue2;
|
|
350
|
-
return shouldBeNullable ? "Decimal | null" : "Decimal";
|
|
351
|
-
}
|
|
352
|
-
return generateStringLikeField();
|
|
353
|
-
}
|
|
354
|
-
if (booleanTypes[schemaType].includes(type)) return generateBooleanLikeField();
|
|
355
|
-
if (schemaType !== "sqlite" && enumTypes[schemaType].includes(type))
|
|
356
|
-
return generateEnumLikeField();
|
|
357
|
-
throw new Error(`Unsupported column type: ${type}`);
|
|
358
|
-
}
|
|
359
|
-
function generateContent({
|
|
360
|
-
table,
|
|
361
|
-
describes,
|
|
362
|
-
config,
|
|
363
|
-
destination,
|
|
364
|
-
isCamelCase,
|
|
365
|
-
enumDeclarations: enumDeclarations2,
|
|
366
|
-
defaultZodHeader: defaultZodHeader2
|
|
367
|
-
}) {
|
|
368
|
-
let content = "";
|
|
369
|
-
const schemaType = config.origin.type;
|
|
370
|
-
if (destination.type === "kysely") {
|
|
371
|
-
content += `// Kysely type definitions for ${table}
|
|
372
|
-
`;
|
|
373
|
-
content += `
|
|
374
|
-
// This interface defines the structure of the '${table}' table
|
|
375
|
-
export interface ${camelCase(table, { pascalCase: true })} {`;
|
|
376
|
-
for (const desc of describes) {
|
|
377
|
-
const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
|
|
378
|
-
const type = getType("table", desc, config, destination, table);
|
|
379
|
-
if (type) {
|
|
380
|
-
let kyselyType = type;
|
|
381
|
-
const isAutoIncrement = desc.Extra.toLowerCase().includes("auto_increment");
|
|
382
|
-
const isDefaultGenerated = desc.Extra.toLowerCase().includes("default_generated");
|
|
383
|
-
const isNullable = desc.Null === "YES";
|
|
384
|
-
const isJsonField = desc.Type.toLowerCase().includes("json");
|
|
385
|
-
const hasDefaultValue = desc.Default !== null;
|
|
386
|
-
const isEnum = schemaType !== "sqlite" && enumTypes[schemaType].includes(
|
|
387
|
-
schemaType === "mysql" ? desc.Type.split("(")[0].split(" ")[0] : desc.Type
|
|
388
|
-
);
|
|
389
|
-
const kyselyOverrideType = config.magicComments ? extractKyselyExpression(desc.Comment) : null;
|
|
390
|
-
if (kyselyOverrideType) {
|
|
391
|
-
kyselyType = kyselyOverrideType;
|
|
392
|
-
if (isNullable && !kyselyType.includes("| null")) {
|
|
393
|
-
kyselyType = `${kyselyType} | null`;
|
|
394
|
-
}
|
|
395
|
-
if (isAutoIncrement || isDefaultGenerated || hasDefaultValue && (isEnum || kyselyType === "string" || kyselyType === "boolean" || kyselyType === "number" || kyselyType === "Decimal" || kyselyType === "BigInt" || kyselyType.includes("boolean | null") || kyselyType.includes("string | null") || kyselyType.includes("number | null") || kyselyType.includes("Decimal | null") || kyselyType.includes("BigInt | null"))) {
|
|
396
|
-
kyselyType = `Generated<${kyselyType}>`;
|
|
397
|
-
}
|
|
398
|
-
} else if (isJsonField) {
|
|
399
|
-
kyselyType = isNullable ? "Json | null" : "Json";
|
|
400
|
-
} else {
|
|
401
|
-
if (isNullable && !isJsonField) {
|
|
402
|
-
if (!kyselyType.includes("| null")) {
|
|
403
|
-
kyselyType = `${kyselyType} | null`;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if (isAutoIncrement || isDefaultGenerated || hasDefaultValue && (isEnum || kyselyType === "string" || kyselyType === "boolean" || kyselyType === "number" || kyselyType === "Decimal" || kyselyType === "BigInt" || kyselyType.includes("boolean | null") || kyselyType.includes("string | null") || kyselyType.includes("number | null") || kyselyType.includes("Decimal | null") || kyselyType.includes("BigInt | null"))) {
|
|
407
|
-
kyselyType = `Generated<${kyselyType}>`;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
content = `${content}
|
|
411
|
-
${field}: ${kyselyType};`;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
content = `${content}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Helper types for ${table}
|
|
418
|
-
export type Selectable${camelCase(table, { pascalCase: true })} = Selectable<${camelCase(table, { pascalCase: true })}>;
|
|
419
|
-
export type Insertable${camelCase(table, { pascalCase: true })} = Insertable<${camelCase(table, { pascalCase: true })}>;
|
|
420
|
-
export type Updateable${camelCase(table, { pascalCase: true })} = Updateable<${camelCase(table, { pascalCase: true })}>;
|
|
421
|
-
`;
|
|
422
|
-
} else if (destination.type === "ts") {
|
|
423
|
-
const modelType = destination.modelType || "interface";
|
|
424
|
-
const isInterface = modelType === "interface";
|
|
425
|
-
const header = destination.header;
|
|
426
|
-
content = header ? `${header}
|
|
427
|
-
|
|
428
|
-
` : "";
|
|
429
|
-
content += `// TypeScript ${isInterface ? "interfaces" : "types"} for ${table}`;
|
|
430
|
-
if (enumDeclarations2[table] && enumDeclarations2[table].length > 0) {
|
|
431
|
-
content += "\n\n// Enum declarations";
|
|
432
|
-
for (const enumDecl of enumDeclarations2[table]) {
|
|
433
|
-
content += `
|
|
434
|
-
${enumDecl}`;
|
|
435
|
-
}
|
|
436
|
-
content += "\n";
|
|
437
|
-
}
|
|
438
|
-
if (isInterface) {
|
|
439
|
-
content += `
|
|
440
|
-
export interface ${camelCase(table, { pascalCase: true })} {`;
|
|
441
|
-
} else {
|
|
442
|
-
content += `
|
|
443
|
-
export type ${camelCase(table, { pascalCase: true })} = {`;
|
|
444
|
-
}
|
|
445
|
-
for (const desc of describes) {
|
|
446
|
-
const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
|
|
447
|
-
const type = getType("table", desc, config, destination, table);
|
|
448
|
-
if (type) {
|
|
449
|
-
content = `${content}
|
|
450
|
-
${field}: ${type};`;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
content = `${content}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
`;
|
|
457
|
-
if (isInterface) {
|
|
458
|
-
content += `export interface Insertable${camelCase(table, { pascalCase: true })} {`;
|
|
459
|
-
} else {
|
|
460
|
-
content += `export type Insertable${camelCase(table, { pascalCase: true })} = {`;
|
|
461
|
-
}
|
|
462
|
-
for (const desc of describes) {
|
|
463
|
-
const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
|
|
464
|
-
const type = getType("insertable", desc, config, destination, table);
|
|
465
|
-
if (type) {
|
|
466
|
-
content = `${content}
|
|
467
|
-
${field}: ${type};`;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
content = `${content}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
`;
|
|
474
|
-
if (isInterface) {
|
|
475
|
-
content += `export interface Updateable${camelCase(table, { pascalCase: true })} {`;
|
|
476
|
-
} else {
|
|
477
|
-
content += `export type Updateable${camelCase(table, { pascalCase: true })} = {`;
|
|
478
|
-
}
|
|
479
|
-
for (const desc of describes) {
|
|
480
|
-
const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
|
|
481
|
-
const type = getType("updateable", desc, config, destination, table);
|
|
482
|
-
if (type) {
|
|
483
|
-
content = `${content}
|
|
484
|
-
${field}: ${type};`;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
content = `${content}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
`;
|
|
491
|
-
if (isInterface) {
|
|
492
|
-
content += `export interface Selectable${camelCase(table, { pascalCase: true })} {`;
|
|
493
|
-
} else {
|
|
494
|
-
content += `export type Selectable${camelCase(table, { pascalCase: true })} = {`;
|
|
495
|
-
}
|
|
496
|
-
for (const desc of describes) {
|
|
497
|
-
const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
|
|
498
|
-
const type = getType("selectable", desc, config, destination, table);
|
|
499
|
-
if (type) {
|
|
500
|
-
content = `${content}
|
|
501
|
-
${field}: ${type};`;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
content = `${content}
|
|
505
|
-
}
|
|
506
|
-
`;
|
|
507
|
-
} else if (destination.type === "zod") {
|
|
508
|
-
const header = destination.header;
|
|
509
|
-
content = header ? header + "\n\n" : defaultZodHeader2(destination.version || 3);
|
|
510
|
-
content += `export const ${table} = z.object({`;
|
|
511
|
-
for (const desc of describes) {
|
|
512
|
-
const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
|
|
513
|
-
const type = getType("table", desc, config, destination, table);
|
|
514
|
-
if (type) {
|
|
515
|
-
content = `${content}
|
|
516
|
-
${field}: ${type},`;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
content = `${content}
|
|
520
|
-
})
|
|
521
|
-
|
|
522
|
-
export const insertable_${table} = z.object({`;
|
|
523
|
-
for (const desc of describes) {
|
|
524
|
-
const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
|
|
525
|
-
const type = getType("insertable", desc, config, destination, table);
|
|
526
|
-
if (type) {
|
|
527
|
-
content = `${content}
|
|
528
|
-
${field}: ${type},`;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
content = `${content}
|
|
532
|
-
})
|
|
533
|
-
|
|
534
|
-
export const updateable_${table} = z.object({`;
|
|
535
|
-
for (const desc of describes) {
|
|
536
|
-
const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
|
|
537
|
-
const type = getType("updateable", desc, config, destination, table);
|
|
538
|
-
if (type) {
|
|
539
|
-
content = `${content}
|
|
540
|
-
${field}: ${type},`;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
content = `${content}
|
|
544
|
-
})
|
|
545
|
-
|
|
546
|
-
export const selectable_${table} = z.object({`;
|
|
547
|
-
for (const desc of describes) {
|
|
548
|
-
const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
|
|
549
|
-
const type = getType("selectable", desc, config, destination, table);
|
|
550
|
-
if (type) {
|
|
551
|
-
content = `${content}
|
|
552
|
-
${field}: ${type},`;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
content = `${content}
|
|
556
|
-
})
|
|
557
|
-
|
|
558
|
-
export type ${camelCase(`${table}Type`, {
|
|
559
|
-
pascalCase: true
|
|
560
|
-
})} = z.infer<typeof ${table}>
|
|
561
|
-
export type Insertable${camelCase(`${table}Type`, {
|
|
562
|
-
pascalCase: true
|
|
563
|
-
})} = z.infer<typeof insertable_${table}>
|
|
564
|
-
export type Updateable${camelCase(`${table}Type`, {
|
|
565
|
-
pascalCase: true
|
|
566
|
-
})} = z.infer<typeof updateable_${table}>
|
|
567
|
-
export type Selectable${camelCase(`${table}Type`, {
|
|
568
|
-
pascalCase: true
|
|
569
|
-
})} = z.infer<typeof selectable_${table}>
|
|
570
|
-
`;
|
|
571
|
-
}
|
|
572
|
-
return content;
|
|
573
|
-
}
|
|
574
|
-
const defaultKyselyHeader = "import { ColumnType, Selectable, Insertable, Updateable } from 'kysely';\n\n";
|
|
575
|
-
const defaultZodHeader = (version) => "import { z } from 'zod" + (version === 3 ? "" : "/v4") + "';\n\n";
|
|
3
|
+
import * as fs from "fs-extra";
|
|
4
|
+
import { filterTables, filterViews, createEntityList } from "./utils/filters.js";
|
|
5
|
+
import { generateContent, generateViewContent } from "./generators/content-generator.js";
|
|
6
|
+
import {
|
|
7
|
+
createDatabaseConnection,
|
|
8
|
+
extractTables,
|
|
9
|
+
extractViews,
|
|
10
|
+
extractColumnDescriptions
|
|
11
|
+
} from "./database/connection.js";
|
|
12
|
+
import {
|
|
13
|
+
extractPrismaEntities,
|
|
14
|
+
extractPrismaColumnDescriptions
|
|
15
|
+
} from "./database/prisma.js";
|
|
16
|
+
import { defaultKyselyHeader, defaultZodHeader, kyselyJsonTypes } from "./constants.js";
|
|
17
|
+
import {
|
|
18
|
+
extractTypeExpression,
|
|
19
|
+
extractTSExpression,
|
|
20
|
+
extractKyselyExpression,
|
|
21
|
+
extractZodExpression
|
|
22
|
+
} from "./utils/magic-comments.js";
|
|
23
|
+
import { generateContent as generateContent2, generateViewContent as generateViewContent2 } from "./generators/content-generator.js";
|
|
24
|
+
import { getType } from "./generators/type-generator.js";
|
|
576
25
|
async function generate(config) {
|
|
577
26
|
let tables = [];
|
|
578
|
-
let
|
|
579
|
-
let
|
|
27
|
+
let views = [];
|
|
28
|
+
let enumDeclarations = {};
|
|
580
29
|
let db = null;
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
db = knex({
|
|
588
|
-
client: "mysql2",
|
|
589
|
-
connection: {
|
|
590
|
-
host: config.origin.host,
|
|
591
|
-
port: config.origin.port,
|
|
592
|
-
user: config.origin.user,
|
|
593
|
-
password: config.origin.password,
|
|
594
|
-
database: config.origin.database,
|
|
595
|
-
ssl: config.origin.ssl
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
} else if (config.origin.type === "postgres") {
|
|
599
|
-
db = knex({
|
|
600
|
-
client: "pg",
|
|
601
|
-
connection: {
|
|
602
|
-
host: config.origin.host,
|
|
603
|
-
port: config.origin.port,
|
|
604
|
-
user: config.origin.user,
|
|
605
|
-
password: config.origin.password,
|
|
606
|
-
database: config.origin.database,
|
|
607
|
-
ssl: config.origin.ssl
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
} else if (config.origin.type === "sqlite") {
|
|
611
|
-
db = knex({
|
|
612
|
-
client: "sqlite3",
|
|
613
|
-
connection: {
|
|
614
|
-
filename: config.origin.path
|
|
615
|
-
},
|
|
616
|
-
useNullAsDefault: true
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
const isCamelCase = config.camelCase && config.camelCase === true;
|
|
620
|
-
if (config.origin.type === "prisma") {
|
|
621
|
-
const schemaContents = readFileSync(config.origin.path).toString();
|
|
622
|
-
schema = createPrismaSchemaBuilder(schemaContents);
|
|
623
|
-
prismaTables = schema.findAllByType("model", {});
|
|
624
|
-
tables = prismaTables.filter((t) => t !== null).map((table) => table.name);
|
|
625
|
-
} else if (config.origin.type === "mysql" && db) {
|
|
626
|
-
const t = await db.raw(
|
|
627
|
-
"SELECT table_name as table_name FROM information_schema.tables WHERE table_schema = ?",
|
|
628
|
-
[config.origin.database]
|
|
629
|
-
);
|
|
630
|
-
tables = t[0].map((row) => row.table_name).sort();
|
|
631
|
-
} else if (config.origin.type === "postgres" && db) {
|
|
632
|
-
const schema2 = config.origin.schema || "public";
|
|
633
|
-
const t = await db.raw(
|
|
634
|
-
"SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND table_type = ?",
|
|
635
|
-
[schema2, "BASE TABLE"]
|
|
636
|
-
);
|
|
637
|
-
tables = t.rows.map((row) => row.table_name).sort();
|
|
638
|
-
} else if (config.origin.type === "sqlite" && db) {
|
|
639
|
-
const t = await db.raw(
|
|
640
|
-
"SELECT name as table_name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'"
|
|
641
|
-
);
|
|
642
|
-
tables = t.map((row) => row.table_name).sort();
|
|
643
|
-
}
|
|
644
|
-
const dests = [];
|
|
645
|
-
const includedTables = config.tables;
|
|
646
|
-
if (includedTables?.length)
|
|
647
|
-
tables = tables.filter((table) => includedTables.includes(table));
|
|
648
|
-
const allIgnoredTables = config.ignore;
|
|
649
|
-
const ignoredTablesRegex = allIgnoredTables?.filter((ignoreString) => {
|
|
650
|
-
return ignoreString.startsWith("/") && ignoreString.endsWith("/");
|
|
651
|
-
});
|
|
652
|
-
const ignoredTableNames = allIgnoredTables?.filter(
|
|
653
|
-
(table) => !ignoredTablesRegex?.includes(table)
|
|
654
|
-
);
|
|
655
|
-
if (ignoredTableNames?.length)
|
|
656
|
-
tables = tables.filter((table) => !ignoredTableNames.includes(table));
|
|
657
|
-
if (ignoredTablesRegex?.length) {
|
|
658
|
-
tables = tables.filter((table) => {
|
|
659
|
-
let useTable = true;
|
|
660
|
-
for (const text of ignoredTablesRegex) {
|
|
661
|
-
const pattern = text.substring(1, text.length - 1);
|
|
662
|
-
if (table.match(pattern) !== null) useTable = false;
|
|
663
|
-
}
|
|
664
|
-
return useTable;
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
let describes = [];
|
|
668
|
-
for (let table of tables.sort((a, b) => a.localeCompare(b))) {
|
|
669
|
-
if (config.origin.type === "mysql" && db) {
|
|
670
|
-
const d = await db.raw(`SHOW FULL COLUMNS FROM ${table}`);
|
|
671
|
-
describes = d[0];
|
|
672
|
-
} else if (config.origin.type === "postgres" && db) {
|
|
673
|
-
const schema2 = config.origin.schema || "public";
|
|
674
|
-
const d = await db.raw(
|
|
675
|
-
`
|
|
676
|
-
SELECT
|
|
677
|
-
column_name as "Field",
|
|
678
|
-
column_default as "Default",
|
|
679
|
-
CASE WHEN is_nullable = 'YES' THEN 'YES' ELSE 'NO' END as "Null",
|
|
680
|
-
data_type as "Type",
|
|
681
|
-
CASE
|
|
682
|
-
WHEN column_default LIKE 'nextval(%' THEN 'auto_increment'
|
|
683
|
-
WHEN column_default IS NOT NULL AND (
|
|
684
|
-
column_default LIKE 'now()%' OR
|
|
685
|
-
column_default LIKE 'uuid_generate_v4()%' OR
|
|
686
|
-
column_default LIKE 'gen_random_uuid()%' OR
|
|
687
|
-
column_default LIKE 'current_timestamp%' OR
|
|
688
|
-
column_default LIKE 'current_date%' OR
|
|
689
|
-
column_default LIKE 'current_time%' OR
|
|
690
|
-
column_default LIKE '(%' OR
|
|
691
|
-
column_default LIKE 'array[%' OR
|
|
692
|
-
column_default LIKE 'json_build_%'
|
|
693
|
-
) THEN 'DEFAULT_GENERATED'
|
|
694
|
-
ELSE ''
|
|
695
|
-
END as "Extra",
|
|
696
|
-
col_description(('"'||$1||'"."'||$2||'"')::regclass::oid, ordinal_position) as "Comment"
|
|
697
|
-
FROM
|
|
698
|
-
information_schema.columns
|
|
699
|
-
WHERE
|
|
700
|
-
table_schema = $1 AND table_name = $2
|
|
701
|
-
ORDER BY
|
|
702
|
-
ordinal_position
|
|
703
|
-
`,
|
|
704
|
-
[schema2, table]
|
|
705
|
-
);
|
|
706
|
-
for (const column of d.rows) {
|
|
707
|
-
if (column.Type === "USER-DEFINED") {
|
|
708
|
-
const enumValues = await db.raw(
|
|
709
|
-
`
|
|
710
|
-
SELECT
|
|
711
|
-
e.enumlabel
|
|
712
|
-
FROM
|
|
713
|
-
pg_type t
|
|
714
|
-
JOIN pg_enum e ON t.oid = e.enumtypid
|
|
715
|
-
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
716
|
-
WHERE
|
|
717
|
-
t.typname = (
|
|
718
|
-
SELECT udt_name
|
|
719
|
-
FROM information_schema.columns
|
|
720
|
-
WHERE table_schema = $1
|
|
721
|
-
AND table_name = $2
|
|
722
|
-
AND column_name = $3
|
|
723
|
-
)
|
|
724
|
-
ORDER BY
|
|
725
|
-
e.enumlabel
|
|
726
|
-
`,
|
|
727
|
-
[schema2, table, column.Field]
|
|
728
|
-
);
|
|
729
|
-
column.EnumOptions = enumValues.rows.map(
|
|
730
|
-
(row) => row.enumlabel
|
|
731
|
-
);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
describes = d.rows;
|
|
735
|
-
} else if (config.origin.type === "sqlite" && db) {
|
|
736
|
-
const d = await db.raw(`PRAGMA table_info(${table})`);
|
|
737
|
-
describes = d.map(
|
|
738
|
-
(row) => {
|
|
739
|
-
let extra = "";
|
|
740
|
-
if (row.dflt_value !== null) {
|
|
741
|
-
if (row.name === "rowid" || row.type.toLowerCase() === "integer primary key") {
|
|
742
|
-
extra = "auto_increment";
|
|
743
|
-
} else if (row.dflt_value.includes("CURRENT_TIMESTAMP") || row.dflt_value.includes("CURRENT_DATE") || row.dflt_value.includes("CURRENT_TIME") || row.dflt_value.includes("DATETIME") || row.dflt_value.includes("strftime") || row.dflt_value.includes("random()") || row.dflt_value.includes("(") || row.dflt_value.includes("uuid") || row.dflt_value.includes("json_")) {
|
|
744
|
-
extra = "DEFAULT_GENERATED";
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
return {
|
|
748
|
-
Field: row.name,
|
|
749
|
-
Default: row.dflt_value,
|
|
750
|
-
Null: row.notnull === 0 ? "YES" : "NO",
|
|
751
|
-
Type: row.type.toLowerCase(),
|
|
752
|
-
Extra: extra,
|
|
753
|
-
Comment: ""
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
);
|
|
30
|
+
try {
|
|
31
|
+
if (config.origin.type === "prisma") {
|
|
32
|
+
const prismaEntities = extractPrismaEntities(config);
|
|
33
|
+
tables = prismaEntities.tables;
|
|
34
|
+
views = prismaEntities.views;
|
|
35
|
+
enumDeclarations = prismaEntities.enumDeclarations;
|
|
757
36
|
} else {
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
(p) => p.type === "field" && p.array !== true && !p.attributes?.find((a) => a.name === "relation")
|
|
762
|
-
).map((field) => {
|
|
763
|
-
let defaultGenerated = false;
|
|
764
|
-
const defaultValueField = field.attributes ? field.attributes.find((a) => a.name === "default") : null;
|
|
765
|
-
const defaultValue = defaultValueField?.args?.[0].value;
|
|
766
|
-
if (typeof defaultValue === "object" && // @ts-ignore
|
|
767
|
-
defaultValue?.type === "function") {
|
|
768
|
-
defaultGenerated = true;
|
|
769
|
-
}
|
|
770
|
-
const parsedDefaultValue = defaultValue !== void 0 && typeof defaultValue !== "object" ? defaultValue.toString().replace(/"/g, "") : null;
|
|
771
|
-
let fieldType = field.fieldType.toString();
|
|
772
|
-
if (!prismaValidTypes.includes(fieldType) && schema) {
|
|
773
|
-
enumOptions = schema.findAllByType("enum", {
|
|
774
|
-
name: fieldType
|
|
775
|
-
})[0]?.enumerators.filter(
|
|
776
|
-
(e) => e.type === "enumerator"
|
|
777
|
-
).map((e) => {
|
|
778
|
-
const attrs = e.attributes?.find((a) => a.name === "map");
|
|
779
|
-
return attrs?.args ? attrs.args[0].value.toString().replace(/"/g, "") : e.name;
|
|
780
|
-
});
|
|
781
|
-
fieldType = "Enum";
|
|
782
|
-
}
|
|
783
|
-
return {
|
|
784
|
-
Field: field.name,
|
|
785
|
-
Default: parsedDefaultValue,
|
|
786
|
-
EnumOptions: enumOptions,
|
|
787
|
-
Extra: defaultGenerated ? "DEFAULT_GENERATED" : "",
|
|
788
|
-
Type: fieldType,
|
|
789
|
-
Null: field.optional ? "YES" : "NO",
|
|
790
|
-
Comment: field.comment ?? ""
|
|
791
|
-
};
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
if (isCamelCase) table = camelCase(table);
|
|
795
|
-
if (!config.destinations || config.destinations.length === 0) {
|
|
796
|
-
throw new Error("No destinations specified");
|
|
37
|
+
db = createDatabaseConnection(config);
|
|
38
|
+
tables = await extractTables(db, config);
|
|
39
|
+
views = await extractViews(db, config);
|
|
797
40
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
(
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
});
|
|
814
|
-
const suffix = destination.suffix || "";
|
|
815
|
-
const folder = destination.folder || ".";
|
|
816
|
-
const file = suffix !== "" ? `${table}.${suffix}.ts` : `${table}.ts`;
|
|
817
|
-
if (config.dryRun) {
|
|
818
|
-
const absolutePath = path.resolve(path.join(folder, file));
|
|
819
|
-
dryRunOutput[absolutePath] = content;
|
|
41
|
+
tables = filterTables(tables, config.tables, config.ignore);
|
|
42
|
+
if (!config.includeViews) {
|
|
43
|
+
views = [];
|
|
44
|
+
} else {
|
|
45
|
+
views = filterViews(views, config.views, config.ignoreViews);
|
|
46
|
+
}
|
|
47
|
+
const allEntities = createEntityList(tables, views);
|
|
48
|
+
const results = {};
|
|
49
|
+
const isCamelCase = config.camelCase === true;
|
|
50
|
+
const nonKyselyDestinations = config.destinations.filter((d) => d.type !== "kysely");
|
|
51
|
+
for (const entity of allEntities) {
|
|
52
|
+
const { name: entityName, type: entityType } = entity;
|
|
53
|
+
let describes;
|
|
54
|
+
if (config.origin.type === "prisma") {
|
|
55
|
+
describes = extractPrismaColumnDescriptions(config, entityName, enumDeclarations);
|
|
820
56
|
} else {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
}
|
|
887
|
-
consolidatedContent += `
|
|
57
|
+
describes = await extractColumnDescriptions(db, config, entityName);
|
|
58
|
+
}
|
|
59
|
+
if (describes.length === 0) continue;
|
|
60
|
+
for (const destination of nonKyselyDestinations) {
|
|
61
|
+
const content = entityType === "view" ? generateViewContent({
|
|
62
|
+
view: entityName,
|
|
63
|
+
describes: describes.sort((a, b) => a.Field.localeCompare(b.Field)),
|
|
64
|
+
config,
|
|
65
|
+
destination,
|
|
66
|
+
isCamelCase,
|
|
67
|
+
enumDeclarations,
|
|
68
|
+
defaultZodHeader
|
|
69
|
+
}) : generateContent({
|
|
70
|
+
table: entityName,
|
|
71
|
+
describes: describes.sort((a, b) => a.Field.localeCompare(b.Field)),
|
|
72
|
+
config,
|
|
73
|
+
destination,
|
|
74
|
+
isCamelCase,
|
|
75
|
+
enumDeclarations,
|
|
76
|
+
defaultZodHeader
|
|
77
|
+
});
|
|
78
|
+
const suffix = destination.suffix || destination.type;
|
|
79
|
+
const folder = destination.folder || ".";
|
|
80
|
+
const fileName = `${entityName}.${suffix}.ts`;
|
|
81
|
+
const filePath = path.join(folder, fileName);
|
|
82
|
+
results[filePath] = (destination.header || "") + content;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const kyselyDestinations = config.destinations.filter((d) => d.type === "kysely");
|
|
86
|
+
for (const kyselyDestination of kyselyDestinations) {
|
|
87
|
+
const header = kyselyDestination.header || defaultKyselyHeader;
|
|
88
|
+
const schemaName = kyselyDestination.schemaName || "DB";
|
|
89
|
+
let consolidatedContent = `${header}
|
|
90
|
+
${kyselyJsonTypes}`;
|
|
91
|
+
const tableContents = [];
|
|
92
|
+
for (const entity of allEntities) {
|
|
93
|
+
const { name: entityName, type: entityType } = entity;
|
|
94
|
+
let describes;
|
|
95
|
+
if (config.origin.type === "prisma") {
|
|
96
|
+
describes = extractPrismaColumnDescriptions(config, entityName, enumDeclarations);
|
|
97
|
+
} else {
|
|
98
|
+
describes = await extractColumnDescriptions(db, config, entityName);
|
|
99
|
+
}
|
|
100
|
+
if (describes.length === 0) continue;
|
|
101
|
+
const content = entityType === "view" ? generateViewContent({
|
|
102
|
+
view: entityName,
|
|
103
|
+
describes: describes.sort((a, b) => a.Field.localeCompare(b.Field)),
|
|
104
|
+
config,
|
|
105
|
+
destination: kyselyDestination,
|
|
106
|
+
isCamelCase,
|
|
107
|
+
enumDeclarations,
|
|
108
|
+
defaultZodHeader
|
|
109
|
+
}) : generateContent({
|
|
110
|
+
table: entityName,
|
|
111
|
+
describes: describes.sort((a, b) => a.Field.localeCompare(b.Field)),
|
|
112
|
+
config,
|
|
113
|
+
destination: kyselyDestination,
|
|
114
|
+
isCamelCase,
|
|
115
|
+
enumDeclarations,
|
|
116
|
+
defaultZodHeader
|
|
117
|
+
});
|
|
118
|
+
tableContents.push({ table: entityName, content });
|
|
119
|
+
consolidatedContent += content + "\n";
|
|
120
|
+
}
|
|
121
|
+
consolidatedContent += `
|
|
888
122
|
// Database Interface
|
|
889
123
|
export interface ${schemaName} {
|
|
890
124
|
`;
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
125
|
+
const sortedTableEntries = tableContents.map(({ table, content }) => {
|
|
126
|
+
const isView = content.includes("(view");
|
|
127
|
+
const pascalTable = camelCase(table, { pascalCase: true }) + (isView ? "View" : "");
|
|
128
|
+
const tableKey = isCamelCase ? camelCase(table) : table;
|
|
129
|
+
return { tableKey, pascalTable, isView };
|
|
130
|
+
}).sort((a, b) => a.tableKey.localeCompare(b.tableKey));
|
|
131
|
+
for (const { tableKey, pascalTable } of sortedTableEntries) {
|
|
132
|
+
consolidatedContent += ` ${tableKey}: ${pascalTable};
|
|
898
133
|
`;
|
|
134
|
+
}
|
|
135
|
+
consolidatedContent += "}\n";
|
|
136
|
+
const outputFile = kyselyDestination.outFile || path.join(kyselyDestination.folder || ".", "db.ts");
|
|
137
|
+
results[outputFile] = consolidatedContent;
|
|
899
138
|
}
|
|
900
|
-
consolidatedContent += "}\n";
|
|
901
139
|
if (config.dryRun) {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
140
|
+
return results;
|
|
141
|
+
}
|
|
142
|
+
for (const [filePath, content] of Object.entries(results)) {
|
|
143
|
+
const fullPath = path.resolve(filePath);
|
|
144
|
+
await fs.ensureDir(path.dirname(fullPath));
|
|
145
|
+
await fs.writeFile(fullPath, content);
|
|
146
|
+
if (!config.silent) {
|
|
147
|
+
console.log(`Created: ${filePath}`);
|
|
908
148
|
}
|
|
909
|
-
} else {
|
|
910
|
-
const dest = path.resolve(outFile);
|
|
911
|
-
dests.push(dest);
|
|
912
|
-
if (!config.silent) console.log("Created:", dest);
|
|
913
|
-
fs.outputFileSync(dest, consolidatedContent);
|
|
914
149
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
const absolutePath = path.resolve(dest);
|
|
920
|
-
const content = fs.readFileSync(dest, "utf8");
|
|
921
|
-
result2[absolutePath] = content;
|
|
150
|
+
return results;
|
|
151
|
+
} finally {
|
|
152
|
+
if (db) {
|
|
153
|
+
await db.destroy();
|
|
922
154
|
}
|
|
923
|
-
return result2;
|
|
924
|
-
}
|
|
925
|
-
const result = {};
|
|
926
|
-
for (const [key, content] of Object.entries(dryRunOutput)) {
|
|
927
|
-
const absolutePath = path.resolve(key);
|
|
928
|
-
result[absolutePath] = content;
|
|
929
155
|
}
|
|
930
|
-
return result;
|
|
931
156
|
}
|
|
932
157
|
export {
|
|
933
158
|
defaultKyselyHeader,
|
|
@@ -937,6 +162,7 @@ export {
|
|
|
937
162
|
extractTypeExpression,
|
|
938
163
|
extractZodExpression,
|
|
939
164
|
generate,
|
|
940
|
-
generateContent,
|
|
165
|
+
generateContent2 as generateContent,
|
|
166
|
+
generateViewContent2 as generateViewContent,
|
|
941
167
|
getType
|
|
942
168
|
};
|