mutano 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -1,27 +1,890 @@
1
- import * as path from "node:path";
2
- import camelCase from "camelcase";
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";
1
+ import * as path from 'node:path';
2
+ import camelCase from 'camelcase';
3
+ import { writeFile } from 'node:fs/promises';
4
+ import { ensureDir } from 'fs-extra/esm';
5
+ import knex from 'knex';
6
+ import { readFileSync } from 'node:fs';
7
+ import { createPrismaSchemaBuilder } from '@mrleebo/prisma-ast';
8
+
9
+ function filterEntities(entities, included, ignored) {
10
+ let filtered = [...entities];
11
+ if (included?.length) {
12
+ filtered = filtered.filter((entity) => included.includes(entity));
13
+ }
14
+ if (ignored?.length) {
15
+ const ignoredRegex = ignored.filter((ignoreString) => {
16
+ return ignoreString.startsWith("/") && ignoreString.endsWith("/");
17
+ });
18
+ const ignoredNames = ignored.filter(
19
+ (entity) => !ignoredRegex.includes(entity)
20
+ );
21
+ if (ignoredNames.length) {
22
+ filtered = filtered.filter((entity) => !ignoredNames.includes(entity));
23
+ }
24
+ if (ignoredRegex.length) {
25
+ filtered = filtered.filter((entity) => {
26
+ let useEntity = true;
27
+ for (const text of ignoredRegex) {
28
+ const pattern = text.substring(1, text.length - 1);
29
+ if (entity.match(pattern) !== null) useEntity = false;
30
+ }
31
+ return useEntity;
32
+ });
33
+ }
34
+ }
35
+ return filtered;
36
+ }
37
+ function filterTables(tables, includedTables, ignoredTables) {
38
+ return filterEntities(tables, includedTables, ignoredTables);
39
+ }
40
+ function filterViews(views, includedViews, ignoredViews) {
41
+ return filterEntities(views, includedViews, ignoredViews);
42
+ }
43
+ function createEntityList(tables, views) {
44
+ const allEntities = [
45
+ ...tables.map((name) => ({ name, type: "table" })),
46
+ ...views.map((name) => ({ name, type: "view" }))
47
+ ];
48
+ return allEntities.sort((a, b) => a.name.localeCompare(b.name));
49
+ }
50
+
51
+ const dateTypes = {
52
+ mysql: ["date", "datetime", "timestamp"],
53
+ postgres: [
54
+ "timestamp",
55
+ "timestamp with time zone",
56
+ "timestamp without time zone",
57
+ "date"
58
+ ],
59
+ sqlite: ["date", "datetime"],
60
+ prisma: ["DateTime"]
61
+ };
62
+ const stringTypes = {
63
+ mysql: [
64
+ "tinytext",
65
+ "text",
66
+ "mediumtext",
67
+ "longtext",
68
+ "json",
69
+ "time",
70
+ "year",
71
+ "char",
72
+ "varchar"
73
+ ],
74
+ postgres: [
75
+ "text",
76
+ "character varying",
77
+ "varchar",
78
+ "char",
79
+ "character",
80
+ "json",
81
+ "jsonb",
82
+ "uuid",
83
+ "time",
84
+ "timetz",
85
+ "interval",
86
+ "name",
87
+ "citext"
88
+ ],
89
+ sqlite: [
90
+ "text",
91
+ "character",
92
+ "varchar",
93
+ "varying character",
94
+ "nchar",
95
+ "native character",
96
+ "nvarchar",
97
+ "clob",
98
+ "json"
99
+ ],
100
+ prisma: ["String", "Bytes", "Json"]
101
+ };
102
+ const bigIntTypes = {
103
+ mysql: ["bigint"],
104
+ postgres: ["bigint"],
105
+ sqlite: ["bigint", "unsigned big int", "int8"],
106
+ prisma: ["BigInt"]
107
+ };
108
+ const numberTypes = {
109
+ mysql: [
110
+ "tinyint",
111
+ "smallint",
112
+ "mediumint",
113
+ "int",
114
+ "float",
115
+ "double",
116
+ "bit",
117
+ "year"
118
+ ],
119
+ postgres: [
120
+ "smallint",
121
+ "integer",
122
+ "real",
123
+ "double precision",
124
+ "smallserial",
125
+ "serial",
126
+ "bigserial",
127
+ "bit",
128
+ "bit varying"
129
+ ],
130
+ sqlite: [
131
+ "integer",
132
+ "real",
133
+ "numeric",
134
+ "double",
135
+ "double precision",
136
+ "float",
137
+ "int",
138
+ "int2",
139
+ "mediumint",
140
+ "tinyint",
141
+ "smallint"
142
+ ],
143
+ prisma: ["Int", "Float"]
144
+ };
145
+ const decimalTypes = {
146
+ mysql: ["decimal"],
147
+ postgres: ["decimal", "numeric", "money"],
148
+ sqlite: ["decimal"],
149
+ prisma: ["Decimal"]
150
+ };
151
+ const booleanTypes = {
152
+ mysql: ["boolean"],
153
+ postgres: ["boolean"],
154
+ sqlite: ["boolean"],
155
+ prisma: ["Boolean"]
156
+ };
157
+ const enumTypes = {
158
+ mysql: ["enum"],
159
+ postgres: ["enum", "USER-DEFINED"],
160
+ sqlite: [],
161
+ prisma: []
162
+ };
163
+ const enumRegex = /enum\(([^)]+)\)/;
164
+ function getTypeMappings(dbType) {
165
+ return {
166
+ dateTypes: dateTypes[dbType],
167
+ stringTypes: stringTypes[dbType],
168
+ bigIntTypes: bigIntTypes[dbType],
169
+ numberTypes: numberTypes[dbType],
170
+ decimalTypes: decimalTypes[dbType],
171
+ booleanTypes: booleanTypes[dbType],
172
+ enumTypes: enumTypes[dbType]
173
+ };
174
+ }
175
+ function isJsonType(type) {
176
+ return type.toLowerCase().includes("json");
177
+ }
178
+
179
+ const extractTypeExpression = (comment, prefix) => {
180
+ const start = comment.indexOf(prefix);
181
+ if (start === -1) return null;
182
+ const typeLen = prefix.length;
183
+ let position = start + typeLen;
184
+ let depth = 1;
185
+ while (position < comment.length && depth > 0) {
186
+ const char = comment[position];
187
+ if (char === "(" || char === "{" || char === "<" || char === "[") {
188
+ depth++;
189
+ } else if (char === ")" || char === "}" || char === ">" || char === "]") {
190
+ depth--;
191
+ if (depth === 0) {
192
+ const extracted = comment.substring(start + typeLen, position);
193
+ return extracted;
194
+ }
195
+ }
196
+ position++;
197
+ }
198
+ return null;
199
+ };
200
+ const extractTSExpression = (comment) => extractTypeExpression(comment, "@ts(");
201
+ const extractKyselyExpression = (comment) => extractTypeExpression(comment, "@kysely(");
202
+ const extractZodExpression = (comment) => extractTypeExpression(comment, "@zod(");
203
+
204
+ function getType(op, desc, config, destination) {
205
+ const { Default, Extra, Null, Type, Comment, EnumOptions } = desc;
206
+ const schemaType = config.origin.type;
207
+ const type = schemaType === "prisma" ? Type : Type.toLowerCase();
208
+ const isNull = Null === "YES";
209
+ const hasDefaultValue = Default !== null;
210
+ const isGenerated = Extra.toLowerCase().includes("auto_increment") || Extra.toLowerCase().includes("default_generated");
211
+ const isTsDestination = destination.type === "ts";
212
+ const isKyselyDestination = destination.type === "kysely";
213
+ const isZodDestination = destination.type === "zod";
214
+ const typeMappings = getTypeMappings(schemaType);
215
+ if (isTsDestination || isKyselyDestination) {
216
+ const isJsonField = isJsonType(type);
217
+ if (isKyselyDestination && isJsonField) {
218
+ if (config.magicComments) {
219
+ const kyselyOverrideType = extractKyselyExpression(Comment);
220
+ if (kyselyOverrideType) {
221
+ const shouldBeNullable2 = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
222
+ return shouldBeNullable2 ? kyselyOverrideType.includes("| null") ? kyselyOverrideType : `${kyselyOverrideType} | null` : kyselyOverrideType;
223
+ }
224
+ }
225
+ const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
226
+ return shouldBeNullable ? "Json | null" : "Json";
227
+ }
228
+ if (isKyselyDestination && config.magicComments) {
229
+ const kyselyOverrideType = extractKyselyExpression(Comment);
230
+ if (kyselyOverrideType) {
231
+ const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
232
+ return shouldBeNullable ? kyselyOverrideType.includes("| null") ? kyselyOverrideType : `${kyselyOverrideType} | null` : kyselyOverrideType;
233
+ }
234
+ }
235
+ if ((isTsDestination || isKyselyDestination) && config.magicComments) {
236
+ const tsOverrideType = extractTSExpression(Comment);
237
+ if (tsOverrideType) {
238
+ const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
239
+ return shouldBeNullable ? tsOverrideType.includes("| null") ? tsOverrideType : `${tsOverrideType} | null` : tsOverrideType;
240
+ }
241
+ }
242
+ }
243
+ if (isZodDestination && config.magicComments) {
244
+ const zodOverrideType = extractZodExpression(Comment);
245
+ if (zodOverrideType) {
246
+ const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
247
+ const nullishOption = destination.nullish;
248
+ const nullableMethod = nullishOption ? "nullish" : "nullable";
249
+ return shouldBeNullable ? zodOverrideType.includes(`.${nullableMethod}()`) || zodOverrideType.includes(".optional()") ? zodOverrideType : `${zodOverrideType}.${nullableMethod}()` : zodOverrideType;
250
+ }
251
+ }
252
+ const overrideTypes = config.origin.overrideTypes;
253
+ if (overrideTypes && overrideTypes[Type]) {
254
+ const overrideType = overrideTypes[Type];
255
+ const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
256
+ if (isZodDestination) {
257
+ const nullishOption = destination.nullish;
258
+ const nullableMethod = nullishOption ? "nullish" : "nullable";
259
+ return shouldBeNullable ? `${overrideType}.${nullableMethod}()` : overrideType;
260
+ } else {
261
+ return shouldBeNullable ? `${overrideType} | null` : overrideType;
262
+ }
263
+ }
264
+ const enumTypesForSchema = typeMappings.enumTypes[schemaType] || [];
265
+ const isEnum = enumTypesForSchema.includes(type);
266
+ if (isEnum) {
267
+ let enumValues = [];
268
+ if (schemaType === "mysql" && type === "enum") {
269
+ const match = Type.match(enumRegex);
270
+ if (match) {
271
+ enumValues = match[1].split(",").map((v) => v.trim().replace(/'/g, ""));
272
+ }
273
+ } else if (schemaType === "postgres" && EnumOptions) {
274
+ enumValues = EnumOptions;
275
+ }
276
+ if (enumValues.length > 0) {
277
+ const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
278
+ if (isZodDestination) {
279
+ const enumString = `z.enum([${enumValues.map((v) => `'${v}'`).join(",")}])`;
280
+ const nullishOption = destination.nullish;
281
+ const nullableMethod = nullishOption ? "nullish" : "nullable";
282
+ return shouldBeNullable ? `${enumString}.${nullableMethod}()` : enumString;
283
+ } else if (isTsDestination) {
284
+ const enumType = destination.enumType;
285
+ if (enumType === "enum") {
286
+ const enumString = enumValues.map((v) => `'${v}'`).join(" | ");
287
+ return shouldBeNullable ? `${enumString} | null` : enumString;
288
+ } else {
289
+ const enumString = enumValues.map((v) => `'${v}'`).join(" | ");
290
+ return shouldBeNullable ? `${enumString} | null` : enumString;
291
+ }
292
+ } else if (isKyselyDestination) {
293
+ const enumString = enumValues.map((v) => `'${v}'`).join(" | ");
294
+ return shouldBeNullable ? `${enumString} | null` : enumString;
295
+ }
296
+ }
297
+ }
298
+ return generateStandardType(op, desc, config, destination, typeMappings);
299
+ }
300
+ function generateStandardType(op, desc, config, destination, typeMappings) {
301
+ const { Default, Extra, Null, Type } = desc;
302
+ const schemaType = config.origin.type;
303
+ const type = schemaType === "prisma" ? Type : Type.toLowerCase();
304
+ const isNull = Null === "YES";
305
+ const hasDefaultValue = Default !== null;
306
+ const isGenerated = Extra.toLowerCase().includes("auto_increment") || Extra.toLowerCase().includes("default_generated");
307
+ const isZodDestination = destination.type === "zod";
308
+ const isKyselyDestination = destination.type === "kysely";
309
+ const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
310
+ let baseType;
311
+ if (typeMappings.dateTypes.includes(type)) {
312
+ if (isZodDestination) {
313
+ const useDateType = destination.useDateType;
314
+ if (useDateType) {
315
+ baseType = "z.union([z.number(), z.string(), z.date()]).pipe(z.coerce.date())";
316
+ } else {
317
+ baseType = "z.date()";
318
+ }
319
+ } else {
320
+ baseType = "Date";
321
+ }
322
+ } else if (typeMappings.bigIntTypes.includes(type)) {
323
+ if (isZodDestination) {
324
+ baseType = "z.string()";
325
+ } else if (isKyselyDestination) {
326
+ baseType = "BigInt";
327
+ } else {
328
+ baseType = "string";
329
+ }
330
+ } else if (typeMappings.decimalTypes.includes(type)) {
331
+ if (isZodDestination) {
332
+ baseType = "z.string()";
333
+ } else if (isKyselyDestination) {
334
+ baseType = "Decimal";
335
+ } else {
336
+ baseType = "string";
337
+ }
338
+ } else if (typeMappings.numberTypes.includes(type)) {
339
+ if (isZodDestination) {
340
+ baseType = "z.number()";
341
+ if (!shouldBeNullable && !hasDefaultValue) {
342
+ baseType += ".nonnegative()";
343
+ }
344
+ } else {
345
+ baseType = "number";
346
+ }
347
+ } else if (typeMappings.booleanTypes.includes(type)) {
348
+ baseType = isZodDestination ? "z.boolean()" : "boolean";
349
+ } else if (typeMappings.stringTypes.includes(type)) {
350
+ if (isZodDestination) {
351
+ const useTrim = destination.useTrim;
352
+ const requiredString = destination.requiredString;
353
+ baseType = "z.string()";
354
+ if (useTrim) baseType += ".trim()";
355
+ if (requiredString && !shouldBeNullable) baseType += ".min(1)";
356
+ } else {
357
+ baseType = "string";
358
+ }
359
+ } else {
360
+ baseType = isZodDestination ? "z.string()" : "string";
361
+ }
362
+ if (shouldBeNullable) {
363
+ if (isZodDestination) {
364
+ const nullishOption = destination.nullish;
365
+ const nullableMethod = nullishOption ? "nullish" : "nullable";
366
+ return `${baseType}.${nullableMethod}()`;
367
+ } else {
368
+ return `${baseType} | null`;
369
+ }
370
+ }
371
+ return baseType;
372
+ }
373
+
374
+ function generateViewContent({
375
+ view,
376
+ describes,
377
+ config,
378
+ destination,
379
+ isCamelCase,
380
+ enumDeclarations: _enumDeclarations,
381
+ defaultZodHeader
382
+ }) {
383
+ let content = "";
384
+ if (destination.type === "kysely") {
385
+ const pascalView = camelCase(view, { pascalCase: true });
386
+ content += `// Kysely type definitions for ${view} (view)
387
+
388
+ `;
389
+ content += `// This interface defines the structure of the '${view}' view (read-only)
390
+ `;
391
+ content += `export interface ${pascalView}View {
392
+ `;
393
+ for (const desc of describes) {
394
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
395
+ const fieldType = getType("selectable", desc, config, destination);
396
+ content += ` ${fieldName}: ${fieldType};
397
+ `;
398
+ }
399
+ content += "}\n\n";
400
+ content += `// Helper types for ${view} (view - read-only)
401
+ `;
402
+ content += `export type Selectable${pascalView}View = Selectable<${pascalView}View>;
403
+ `;
404
+ } else if (destination.type === "ts") {
405
+ const pascalView = camelCase(view, { pascalCase: true });
406
+ content += `// TypeScript interface for ${view} (view - read-only)
407
+ `;
408
+ content += `export interface ${pascalView}View {
409
+ `;
410
+ for (const desc of describes) {
411
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
412
+ const fieldType = getType("selectable", desc, config, destination);
413
+ content += ` ${fieldName}: ${fieldType};
414
+ `;
415
+ }
416
+ content += "}\n";
417
+ } else if (destination.type === "zod") {
418
+ const version = destination.version || 3;
419
+ const header = destination.header || defaultZodHeader(version);
420
+ if (!content.includes(header)) {
421
+ content += header;
422
+ }
423
+ content += `// View schema (read-only)
424
+ `;
425
+ content += `export const ${view}_view = z.object({
426
+ `;
427
+ for (const desc of describes) {
428
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
429
+ const fieldType = getType("selectable", desc, config, destination);
430
+ content += ` ${fieldName}: ${fieldType},
431
+ `;
432
+ }
433
+ content += "})\n\n";
434
+ const pascalView = camelCase(view, { pascalCase: true });
435
+ content += `export type ${camelCase(`${pascalView}ViewType`, {
436
+ pascalCase: true
437
+ })} = z.infer<typeof ${view}_view>
438
+ `;
439
+ }
440
+ return content;
441
+ }
442
+ function generateContent({
443
+ table,
444
+ describes,
445
+ config,
446
+ destination,
447
+ isCamelCase,
448
+ enumDeclarations: _enumDeclarations,
449
+ defaultZodHeader
450
+ }) {
451
+ let content = "";
452
+ if (destination.type === "ts") {
453
+ return generateTypeScriptContent({
454
+ table,
455
+ describes,
456
+ config,
457
+ destination,
458
+ isCamelCase
459
+ });
460
+ } else if (destination.type === "kysely") {
461
+ return generateKyselyContent({
462
+ table,
463
+ describes,
464
+ config,
465
+ destination,
466
+ isCamelCase
467
+ });
468
+ } else if (destination.type === "zod") {
469
+ return generateZodContent({
470
+ table,
471
+ describes,
472
+ config,
473
+ destination,
474
+ isCamelCase,
475
+ defaultZodHeader
476
+ });
477
+ }
478
+ return content;
479
+ }
480
+ function generateTypeScriptContent({
481
+ table,
482
+ describes,
483
+ config,
484
+ destination,
485
+ isCamelCase
486
+ }) {
487
+ let content = "";
488
+ const pascalTable = camelCase(table, { pascalCase: true });
489
+ content += `// TypeScript interfaces for ${table}
490
+
491
+ `;
492
+ content += `export interface ${pascalTable} {
493
+ `;
494
+ for (const desc of describes) {
495
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
496
+ const fieldType = getType("table", desc, config, destination);
497
+ content += ` ${fieldName}: ${fieldType};
498
+ `;
499
+ }
500
+ content += "}\n\n";
501
+ content += `export interface Insertable${pascalTable} {
502
+ `;
503
+ for (const desc of describes) {
504
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
505
+ const fieldType = getType("insertable", desc, config, destination);
506
+ content += ` ${fieldName}: ${fieldType};
507
+ `;
508
+ }
509
+ content += "}\n\n";
510
+ content += `export interface Updateable${pascalTable} {
511
+ `;
512
+ for (const desc of describes) {
513
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
514
+ const fieldType = getType("updateable", desc, config, destination);
515
+ content += ` ${fieldName}: ${fieldType};
516
+ `;
517
+ }
518
+ content += "}\n\n";
519
+ content += `export interface Selectable${pascalTable} {
520
+ `;
521
+ for (const desc of describes) {
522
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
523
+ const fieldType = getType("selectable", desc, config, destination);
524
+ content += ` ${fieldName}: ${fieldType};
525
+ `;
526
+ }
527
+ content += "}\n";
528
+ return content;
529
+ }
530
+ function generateKyselyContent({
531
+ table,
532
+ describes,
533
+ config,
534
+ destination,
535
+ isCamelCase
536
+ }) {
537
+ let content = "";
538
+ const pascalTable = camelCase(table, { pascalCase: true });
539
+ content += `// Kysely type definitions for ${table}
540
+
541
+ `;
542
+ content += `// This interface defines the structure of the '${table}' table
543
+ `;
544
+ content += `export interface ${pascalTable}Table {
545
+ `;
546
+ for (const desc of describes) {
547
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
548
+ let fieldType = getType("table", desc, config, destination);
549
+ const isAutoIncrement = desc.Extra.toLowerCase().includes("auto_increment");
550
+ const isDefaultGenerated = desc.Extra.toLowerCase().includes("default_generated");
551
+ if (isAutoIncrement || isDefaultGenerated) {
552
+ fieldType = `Generated<${fieldType.replace(" | null", "")}>${fieldType.includes(" | null") ? " | null" : ""}`;
553
+ }
554
+ content += ` ${fieldName}: ${fieldType};
555
+ `;
556
+ }
557
+ content += "}\n\n";
558
+ content += `// Use these types for inserting, selecting and updating the table
559
+ `;
560
+ content += `export type ${pascalTable} = Selectable<${pascalTable}Table>;
561
+ `;
562
+ content += `export type New${pascalTable} = Insertable<${pascalTable}Table>;
563
+ `;
564
+ content += `export type ${pascalTable}Update = Updateable<${pascalTable}Table>;
565
+ `;
566
+ return content;
567
+ }
568
+ function generateZodContent({
569
+ table,
570
+ describes,
571
+ config,
572
+ destination,
573
+ isCamelCase,
574
+ defaultZodHeader
575
+ }) {
576
+ let content = "";
577
+ const version = destination.version || 3;
578
+ const header = destination.header || defaultZodHeader(version);
579
+ if (!content.includes(header)) {
580
+ content += header;
581
+ }
582
+ content += `export const ${table} = z.object({
583
+ `;
584
+ for (const desc of describes) {
585
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
586
+ const fieldType = getType("table", desc, config, destination);
587
+ content += ` ${fieldName}: ${fieldType},
588
+ `;
589
+ }
590
+ content += "})\n\n";
591
+ content += `export const insertable_${table} = z.object({
592
+ `;
593
+ for (const desc of describes) {
594
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
595
+ const fieldType = getType("insertable", desc, config, destination);
596
+ content += ` ${fieldName}: ${fieldType},
597
+ `;
598
+ }
599
+ content += "})\n\n";
600
+ content += `export const updateable_${table} = z.object({
601
+ `;
602
+ for (const desc of describes) {
603
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
604
+ const fieldType = getType("updateable", desc, config, destination);
605
+ content += ` ${fieldName}: ${fieldType},
606
+ `;
607
+ }
608
+ content += "})\n\n";
609
+ content += `export const selectable_${table} = z.object({
610
+ `;
611
+ for (const desc of describes) {
612
+ const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
613
+ const fieldType = getType("selectable", desc, config, destination);
614
+ content += ` ${fieldName}: ${fieldType},
615
+ `;
616
+ }
617
+ content += "})\n\n";
618
+ content += `export type ${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof ${table}>
619
+ `;
620
+ content += `export type Insertable${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof insertable_${table}>
621
+ `;
622
+ content += `export type Updateable${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof updateable_${table}>
623
+ `;
624
+ content += `export type Selectable${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof selectable_${table}>
625
+ `;
626
+ return content;
627
+ }
628
+
629
+ function createDatabaseConnection(config) {
630
+ const { origin } = config;
631
+ switch (origin.type) {
632
+ case "mysql":
633
+ return knex({
634
+ client: "mysql2",
635
+ connection: {
636
+ host: origin.host,
637
+ port: origin.port,
638
+ user: origin.user,
639
+ password: origin.password,
640
+ database: origin.database,
641
+ ssl: origin.ssl
642
+ }
643
+ });
644
+ case "postgres":
645
+ return knex({
646
+ client: "pg",
647
+ connection: {
648
+ host: origin.host,
649
+ port: origin.port,
650
+ user: origin.user,
651
+ password: origin.password,
652
+ database: origin.database,
653
+ ssl: origin.ssl
654
+ },
655
+ searchPath: origin.schema ? [origin.schema] : ["public"]
656
+ });
657
+ case "sqlite":
658
+ return knex({
659
+ client: "sqlite3",
660
+ connection: {
661
+ filename: origin.path
662
+ },
663
+ useNullAsDefault: true
664
+ });
665
+ default:
666
+ throw new Error(`Unsupported database type: ${origin.type}`);
667
+ }
668
+ }
669
+ async function extractTables(db, config) {
670
+ const { origin } = config;
671
+ switch (origin.type) {
672
+ case "mysql":
673
+ const mysqlTables = await db.raw(`
674
+ SELECT table_name
675
+ FROM information_schema.tables
676
+ WHERE table_schema = ? AND table_type = 'BASE TABLE'
677
+ `, [origin.database]);
678
+ return mysqlTables[0].map((row) => row.table_name);
679
+ case "postgres":
680
+ const schema = origin.schema || "public";
681
+ const postgresTables = await db.raw(`
682
+ SELECT table_name
683
+ FROM information_schema.tables
684
+ WHERE table_schema = ? AND table_type = 'BASE TABLE'
685
+ `, [schema]);
686
+ return postgresTables.rows.map((row) => row.table_name);
687
+ case "sqlite":
688
+ const sqliteTables = await db.raw(`
689
+ SELECT name
690
+ FROM sqlite_master
691
+ WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
692
+ `);
693
+ return sqliteTables.map((row) => row.name);
694
+ default:
695
+ return [];
696
+ }
697
+ }
698
+ async function extractViews(db, config) {
699
+ const { origin } = config;
700
+ switch (origin.type) {
701
+ case "mysql":
702
+ const mysqlViews = await db.raw(`
703
+ SELECT table_name
704
+ FROM information_schema.tables
705
+ WHERE table_schema = ? AND table_type = 'VIEW'
706
+ `, [origin.database]);
707
+ return mysqlViews[0].map((row) => row.table_name);
708
+ case "postgres":
709
+ const schema = origin.schema || "public";
710
+ const postgresViews = await db.raw(`
711
+ SELECT table_name
712
+ FROM information_schema.tables
713
+ WHERE table_schema = ? AND table_type = 'VIEW'
714
+ `, [schema]);
715
+ return postgresViews.rows.map((row) => row.table_name);
716
+ case "sqlite":
717
+ const sqliteViews = await db.raw(`
718
+ SELECT name
719
+ FROM sqlite_master
720
+ WHERE type = 'view'
721
+ `);
722
+ return sqliteViews.map((row) => row.name);
723
+ default:
724
+ return [];
725
+ }
726
+ }
727
+ async function extractColumnDescriptions(db, config, tableName) {
728
+ const { origin } = config;
729
+ switch (origin.type) {
730
+ case "mysql":
731
+ const mysqlColumns = await db.raw(`
732
+ SELECT
733
+ column_name as \`Field\`,
734
+ column_default as \`Default\`,
735
+ extra as \`Extra\`,
736
+ is_nullable as \`Null\`,
737
+ column_type as \`Type\`,
738
+ column_comment as \`Comment\`
739
+ FROM information_schema.columns
740
+ WHERE table_schema = ? AND table_name = ?
741
+ ORDER BY ordinal_position
742
+ `, [origin.database, tableName]);
743
+ return mysqlColumns[0].map((row) => ({
744
+ Field: row.Field,
745
+ Default: row.Default,
746
+ Extra: row.Extra || "",
747
+ Null: row.Null,
748
+ Type: row.Type,
749
+ Comment: row.Comment || ""
750
+ }));
751
+ case "postgres":
752
+ const schema = origin.schema || "public";
753
+ const postgresColumns = await db.raw(`
754
+ SELECT
755
+ column_name as "Field",
756
+ column_default as "Default",
757
+ '' as "Extra",
758
+ is_nullable as "Null",
759
+ data_type as "Type",
760
+ '' as "Comment"
761
+ FROM information_schema.columns
762
+ WHERE table_schema = ? AND table_name = ?
763
+ ORDER BY ordinal_position
764
+ `, [schema, tableName]);
765
+ return postgresColumns.rows.map((row) => ({
766
+ Field: row.Field,
767
+ Default: row.Default,
768
+ Extra: row.Extra || "",
769
+ Null: row.Null,
770
+ Type: row.Type,
771
+ Comment: row.Comment || ""
772
+ }));
773
+ case "sqlite":
774
+ const sqliteColumns = await db.raw(`PRAGMA table_info(${tableName})`);
775
+ return sqliteColumns.map((row) => ({
776
+ Field: row.name,
777
+ Default: row.dflt_value,
778
+ Extra: row.pk ? "PRIMARY KEY" : "",
779
+ Null: row.notnull ? "NO" : "YES",
780
+ Type: row.type,
781
+ Comment: ""
782
+ }));
783
+ default:
784
+ return [];
785
+ }
786
+ }
787
+
788
+ function extractPrismaEntities(config) {
789
+ if (config.origin.type !== "prisma") {
790
+ return { tables: [], views: [], enumDeclarations: {} };
791
+ }
792
+ const schemaContent = readFileSync(config.origin.path, "utf-8");
793
+ const schema = createPrismaSchemaBuilder(schemaContent);
794
+ const prismaModels = schema.findAllByType("model", {});
795
+ const tables = prismaModels.filter((m) => m !== null).map((model) => model.name);
796
+ const prismaViews = schema.findAllByType("view", {});
797
+ const views = prismaViews.filter((v) => v !== null).map((view) => view.name);
798
+ const enumDeclarations = {};
799
+ const prismaEnums = schema.findAllByType("enum", {});
800
+ for (const prismaEnum of prismaEnums) {
801
+ if (prismaEnum && "name" in prismaEnum && "enumerators" in prismaEnum) {
802
+ const enumName = prismaEnum.name;
803
+ const enumerators = prismaEnum.enumerators;
804
+ enumDeclarations[enumName] = enumerators.map((e) => e.name);
805
+ }
806
+ }
807
+ return { tables, views, enumDeclarations };
808
+ }
809
+ function extractPrismaColumnDescriptions(config, entityName, enumDeclarations) {
810
+ if (config.origin.type !== "prisma") {
811
+ return [];
812
+ }
813
+ const schemaContent = readFileSync(config.origin.path, "utf-8");
814
+ const schema = createPrismaSchemaBuilder(schemaContent);
815
+ let entity = schema.findByType("model", { name: entityName });
816
+ if (!entity) {
817
+ entity = schema.findByType("view", { name: entityName });
818
+ }
819
+ if (!entity || !("properties" in entity)) {
820
+ return [];
821
+ }
822
+ const fields = entity.properties.filter(
823
+ (p) => p.type === "field" && p.array !== true && !p.attributes?.find((a) => a.name === "relation")
824
+ );
825
+ return fields.map((field) => {
826
+ let defaultGenerated = false;
827
+ let defaultValue = null;
828
+ if (field.attributes) {
829
+ for (const attr of field.attributes) {
830
+ if (attr.name === "default") {
831
+ if (attr.args && attr.args.length > 0) {
832
+ const arg = attr.args[0];
833
+ if (typeof arg === "object" && "value" in arg) {
834
+ if (arg.value === "autoincrement()" || arg.value === "cuid()" || arg.value === "uuid()") {
835
+ defaultGenerated = true;
836
+ } else {
837
+ defaultValue = String(arg.value);
838
+ }
839
+ }
840
+ }
841
+ }
842
+ }
843
+ }
844
+ const isOptional = field.optional === true;
845
+ let enumOptions;
846
+ const fieldType = String(field.fieldType);
847
+ if (enumDeclarations[fieldType]) {
848
+ enumOptions = enumDeclarations[fieldType];
849
+ }
850
+ return {
851
+ Field: field.name,
852
+ Default: defaultValue,
853
+ Extra: defaultGenerated ? "auto_increment" : "",
854
+ Null: isOptional ? "YES" : "NO",
855
+ Type: fieldType,
856
+ Comment: "",
857
+ // Prisma doesn't have column comments in the same way
858
+ EnumOptions: enumOptions
859
+ };
860
+ });
861
+ }
862
+
863
+ const defaultKyselyHeader = "import { Generated, Insertable, Selectable, Updateable, ColumnType } from 'kysely';\n\n";
864
+ const defaultZodHeader = (version) => "import { z } from 'zod" + (version === 3 ? "" : "/v4") + "';\n\n";
865
+ const kyselyJsonTypes = `// JSON type definitions
866
+ export type Json = ColumnType<JsonValue, string, string>;
867
+
868
+ export type JsonArray = JsonValue[];
869
+
870
+ export type JsonObject = {
871
+ [x: string]: JsonValue | undefined;
872
+ };
873
+
874
+ export type JsonPrimitive = boolean | number | string | null;
875
+
876
+ export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
877
+
878
+ export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
879
+ ? ColumnType<S, I | undefined, U>
880
+ : ColumnType<T, T | undefined, T>
881
+
882
+ export type Decimal = ColumnType<string, number | string, number | string>
883
+
884
+ export type BigInt = ColumnType<string, number | string, number | string>
885
+
886
+ `;
887
+
25
888
  async function generate(config) {
26
889
  let tables = [];
27
890
  let views = [];
@@ -141,8 +1004,8 @@ export interface ${schemaName} {
141
1004
  }
142
1005
  for (const [filePath, content] of Object.entries(results)) {
143
1006
  const fullPath = path.resolve(filePath);
144
- await fs.ensureDir(path.dirname(fullPath));
145
- await fs.writeFile(fullPath, content);
1007
+ await ensureDir(path.dirname(fullPath));
1008
+ await writeFile(fullPath, content);
146
1009
  if (!config.silent) {
147
1010
  console.log(`Created: ${filePath}`);
148
1011
  }
@@ -154,15 +1017,5 @@ export interface ${schemaName} {
154
1017
  }
155
1018
  }
156
1019
  }
157
- export {
158
- defaultKyselyHeader,
159
- defaultZodHeader,
160
- extractKyselyExpression,
161
- extractTSExpression,
162
- extractTypeExpression,
163
- extractZodExpression,
164
- generate,
165
- generateContent2 as generateContent,
166
- generateViewContent2 as generateViewContent,
167
- getType
168
- };
1020
+
1021
+ export { defaultKyselyHeader, defaultZodHeader, extractKyselyExpression, extractTSExpression, extractTypeExpression, extractZodExpression, generate, generateContent, generateViewContent, getType };