mutano 1.1.0 → 2.1.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.
Files changed (4) hide show
  1. package/README.md +412 -33
  2. package/dist/main.d.ts +70 -14
  3. package/dist/main.js +579 -96
  4. package/package.json +31 -29
package/dist/main.js CHANGED
@@ -1,11 +1,33 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
+ const enumDeclarations = {};
3
4
  import {
4
5
  createPrismaSchemaBuilder
5
6
  } from "@mrleebo/prisma-ast";
6
7
  import camelCase from "camelcase";
7
8
  import fs from "fs-extra";
8
9
  import knex from "knex";
10
+ const extractTSExpression = (comment) => {
11
+ const start = comment.indexOf("@ts(");
12
+ if (start === -1) return null;
13
+ const typeLen = 4;
14
+ let position = start + typeLen;
15
+ let depth = 1;
16
+ while (position < comment.length && depth > 0) {
17
+ const char = comment[position];
18
+ if (char === "(" || char === "{" || char === "<" || char === "[") {
19
+ depth++;
20
+ } else if (char === ")" || char === "}" || char === ">" || char === "]") {
21
+ depth--;
22
+ if (depth === 0) {
23
+ const extracted = comment.substring(start + typeLen, position);
24
+ return extracted;
25
+ }
26
+ }
27
+ position++;
28
+ }
29
+ return null;
30
+ };
9
31
  function extractZodExpression(comment) {
10
32
  const zodStart = comment.indexOf("@zod(");
11
33
  if (zodStart === -1) return null;
@@ -38,6 +60,14 @@ const prismaValidTypes = [
38
60
  ];
39
61
  const dateTypes = {
40
62
  mysql: ["date", "datetime", "timestamp"],
63
+ postgres: [
64
+ "date",
65
+ "timestamp",
66
+ "timestamptz",
67
+ "timestamp without time zone",
68
+ "timestamp with time zone"
69
+ ],
70
+ sqlite: ["datetime"],
41
71
  prisma: ["DateTime"]
42
72
  };
43
73
  const stringTypes = {
@@ -53,27 +83,153 @@ const stringTypes = {
53
83
  "char",
54
84
  "varchar"
55
85
  ],
86
+ postgres: [
87
+ "text",
88
+ "character varying",
89
+ "varchar",
90
+ "char",
91
+ "character",
92
+ "json",
93
+ "jsonb",
94
+ "uuid",
95
+ "time",
96
+ "timetz",
97
+ "interval",
98
+ "name",
99
+ "citext",
100
+ "numeric",
101
+ "decimal"
102
+ ],
103
+ sqlite: [
104
+ "text",
105
+ "character",
106
+ "varchar",
107
+ "varying character",
108
+ "nchar",
109
+ "native character",
110
+ "nvarchar",
111
+ "clob",
112
+ "json"
113
+ ],
56
114
  prisma: ["String", "Decimal", "BigInt", "Bytes", "Json"]
57
115
  };
58
116
  const numberTypes = {
59
117
  mysql: ["smallint", "mediumint", "int", "bigint", "float", "double"],
118
+ postgres: [
119
+ "smallint",
120
+ "integer",
121
+ "bigint",
122
+ "decimal",
123
+ "numeric",
124
+ "real",
125
+ "double precision",
126
+ "serial",
127
+ "bigserial"
128
+ ],
129
+ sqlite: [
130
+ "int",
131
+ "integer",
132
+ "tinyint",
133
+ "smallint",
134
+ "mediumint",
135
+ "bigint",
136
+ "unsigned big int",
137
+ "int2",
138
+ "int8",
139
+ "real",
140
+ "double",
141
+ "double precision",
142
+ "float",
143
+ "numeric",
144
+ "decimal"
145
+ ],
60
146
  prisma: ["Int", "Float"]
61
147
  };
62
- const booleanTypes = { mysql: ["tinyint"], prisma: ["Boolean"] };
63
- const enumTypes = { mysql: ["enum"], prisma: ["Enum"] };
64
- function getType(op, desc, config) {
148
+ const booleanTypes = {
149
+ mysql: ["tinyint"],
150
+ postgres: ["boolean", "bool"],
151
+ sqlite: ["boolean"],
152
+ prisma: ["Boolean"]
153
+ };
154
+ const enumTypes = {
155
+ mysql: ["enum"],
156
+ postgres: ["USER-DEFINED"],
157
+ sqlite: [],
158
+ // SQLite doesn't have native enum types
159
+ prisma: ["Enum"]
160
+ };
161
+ const enumRegex = /enum\(([^)]+)\)/;
162
+ function getType(op, desc, config, destination, tableName) {
65
163
  const schemaType = config.origin.type;
66
164
  const { Default, Extra, Null, Type, Comment, EnumOptions } = desc;
67
- const isNullish = config.nullish && config.nullish === true;
68
- const isTrim = config.useTrim && config.useTrim === true && op !== "selectable";
165
+ const isZodDestination = destination.type === "zod";
166
+ const isTsDestination = destination.type === "ts";
167
+ const isKyselyDestination = destination.type === "kysely";
168
+ const isNullish = isZodDestination && destination.type === "zod" && destination.nullish === true;
169
+ const isTrim = isZodDestination && destination.type === "zod" && destination.useTrim === true && op !== "selectable";
170
+ const isUseDateType = isZodDestination && destination.type === "zod" && destination.useDateType === true;
69
171
  const hasDefaultValue = Default !== null && op !== "selectable";
70
172
  const isGenerated = ["DEFAULT_GENERATED", "auto_increment"].includes(Extra);
71
173
  const isNull = Null === "YES";
72
174
  if (isGenerated && !isNull && ["insertable", "updateable"].includes(op))
73
175
  return;
74
- const isRequiredString = config.requiredString && config.requiredString === true && op !== "selectable";
75
- const isUseDateType = config.useDateType && config.useDateType === true;
176
+ const isRequiredString = destination.type === "zod" && destination.requiredString === true && op !== "selectable";
76
177
  const type = schemaType === "mysql" ? Type.split("(")[0].split(" ")[0] : Type;
178
+ if (isTsDestination || isKyselyDestination) {
179
+ const tsOverrideType = config.magicComments ? extractTSExpression(Comment) : null;
180
+ const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
181
+ if (tsOverrideType) {
182
+ return shouldBeNullable ? tsOverrideType.includes("| null") ? tsOverrideType : `${tsOverrideType} | null` : tsOverrideType;
183
+ }
184
+ if (dateTypes[schemaType].includes(type)) {
185
+ return shouldBeNullable ? "Date | null" : "Date";
186
+ }
187
+ if (stringTypes[schemaType].includes(type)) {
188
+ return shouldBeNullable ? "string | null" : "string";
189
+ }
190
+ if (numberTypes[schemaType].includes(type)) {
191
+ return shouldBeNullable ? "number | null" : "number";
192
+ }
193
+ if (booleanTypes[schemaType].includes(type)) {
194
+ return shouldBeNullable ? "boolean | null" : "boolean";
195
+ }
196
+ if (schemaType !== "sqlite" && enumTypes[schemaType].includes(type)) {
197
+ const enumType = destination.type === "ts" ? destination.enumType || "union" : "union";
198
+ let enumValues = [];
199
+ if (schemaType === "mysql") {
200
+ const matches = Type.match(enumRegex);
201
+ if (matches?.[1]) {
202
+ enumValues = matches[1].split(",").map((v) => v.trim());
203
+ }
204
+ } else if (EnumOptions && EnumOptions.length > 0) {
205
+ enumValues = EnumOptions.map((e) => `'${e}'`);
206
+ }
207
+ if (enumValues.length === 0) {
208
+ return isNull ? "string | null" : "string";
209
+ }
210
+ if (enumType === "enum") {
211
+ const enumName = camelCase(`${desc.Field}_enum`, { pascalCase: true });
212
+ const enumDeclaration = `enum ${enumName} {
213
+ ${enumValues.map((v) => {
214
+ const cleanName = v.replace(/['"]/g, "");
215
+ return `${cleanName} = ${v}`;
216
+ }).join(",\n ")}
217
+ }`;
218
+ if (tableName) {
219
+ if (!enumDeclarations[tableName]) {
220
+ enumDeclarations[tableName] = [];
221
+ }
222
+ if (!enumDeclarations[tableName].includes(enumDeclaration)) {
223
+ enumDeclarations[tableName].push(enumDeclaration);
224
+ }
225
+ }
226
+ return shouldBeNullable ? `${enumName} | null` : enumName;
227
+ }
228
+ const unionType = enumValues.join(" | ");
229
+ return shouldBeNullable ? `(${unionType}) | null` : unionType;
230
+ }
231
+ return "any";
232
+ }
77
233
  const zDate = [
78
234
  "z.union([z.number(), z.string(), z.date()]).pipe(z.coerce.date())"
79
235
  ];
@@ -88,8 +244,19 @@ function getType(op, desc, config) {
88
244
  const nonnegative = "nonnegative()";
89
245
  const isUpdateableFormat = op === "updateable" && !isNull && !hasDefaultValue;
90
246
  const min1 = "min(1)";
91
- const zodOverrideType = config.zodCommentTypes ? extractZodExpression(Comment) : null;
92
- const typeOverride = zodOverrideType ?? config.overrideTypes?.[type];
247
+ const zodOverrideType = config.magicComments ? extractZodExpression(Comment) : null;
248
+ let typeOverride = zodOverrideType;
249
+ if (!typeOverride && config.origin.overrideTypes) {
250
+ if (config.origin.type === "mysql") {
251
+ typeOverride = config.origin.overrideTypes[type] || null;
252
+ } else if (config.origin.type === "postgres") {
253
+ typeOverride = config.origin.overrideTypes[type] || null;
254
+ } else if (config.origin.type === "sqlite") {
255
+ typeOverride = config.origin.overrideTypes[type] || null;
256
+ } else if (config.origin.type === "prisma") {
257
+ typeOverride = config.origin.overrideTypes[type] || null;
258
+ }
259
+ }
93
260
  const generateDateLikeField = () => {
94
261
  const field = typeOverride ? [typeOverride] : dateField;
95
262
  if (isNull && !typeOverride) field.push(nullable);
@@ -114,8 +281,13 @@ function getType(op, desc, config) {
114
281
  if (isNull && !typeOverride) field.push(nullable);
115
282
  else if (hasDefaultValue || !hasDefaultValue && isGenerated)
116
283
  field.push(optional);
117
- if (hasDefaultValue && !isGenerated)
118
- field.push(`default(${Boolean(+Default)})`);
284
+ if (hasDefaultValue && !isGenerated) {
285
+ if (Default === "true" || Default === "false") {
286
+ field.push(`default(${Default})`);
287
+ } else {
288
+ field.push(`default(${Boolean(+Default)})`);
289
+ }
290
+ }
119
291
  if (isUpdateableFormat) field.push(optional);
120
292
  return field.join(".");
121
293
  };
@@ -144,36 +316,304 @@ function getType(op, desc, config) {
144
316
  if (stringTypes[schemaType].includes(type)) return generateStringLikeField();
145
317
  if (numberTypes[schemaType].includes(type)) return generateNumberLikeField();
146
318
  if (booleanTypes[schemaType].includes(type)) return generateBooleanLikeField();
147
- if (enumTypes[schemaType].includes(type)) return generateEnumLikeField();
319
+ if (schemaType !== "sqlite" && enumTypes[schemaType].includes(type))
320
+ return generateEnumLikeField();
148
321
  throw new Error(`Unsupported column type: ${type}`);
149
322
  }
323
+ function generateContent({
324
+ table,
325
+ describes,
326
+ config,
327
+ destination,
328
+ isCamelCase,
329
+ enumDeclarations: enumDeclarations2,
330
+ defaultZodHeader: defaultZodHeader2,
331
+ defaultKyselyHeader: defaultKyselyHeader2
332
+ }) {
333
+ let content = "";
334
+ if (destination.type === "kysely") {
335
+ const header = destination.header;
336
+ const schemaName = destination.schemaName || "DB";
337
+ content = header ? `${header}
338
+
339
+ ` : defaultKyselyHeader2;
340
+ content += `// JSON type definitions
341
+ export type Json = ColumnType<JsonValue, string, string>;
342
+
343
+ export type JsonArray = JsonValue[];
344
+
345
+ export type JsonObject = {
346
+ [x: string]: JsonValue | undefined;
347
+ };
348
+
349
+ export type JsonPrimitive = boolean | number | string | null;
350
+
351
+ export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
352
+
353
+ `;
354
+ content += `// Kysely type definitions for ${table}
355
+ `;
356
+ content += `
357
+ // This interface defines the structure of the '${table}' table
358
+ export interface ${camelCase(table, { pascalCase: true })}Table {`;
359
+ for (const desc of describes) {
360
+ const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
361
+ const type = getType("table", desc, config, destination, table);
362
+ if (type) {
363
+ let kyselyType = type;
364
+ const isAutoIncrement = desc.Extra.toLowerCase().includes("auto_increment");
365
+ const isDefaultGenerated = desc.Extra.toLowerCase().includes("default_generated");
366
+ const isNullable = desc.Null === "YES";
367
+ const isJsonField = desc.Type.toLowerCase().includes("json");
368
+ if (isJsonField) {
369
+ kyselyType = "Json";
370
+ } else if (isAutoIncrement || isDefaultGenerated) {
371
+ kyselyType = `Generated<${kyselyType}>`;
372
+ }
373
+ if (isNullable && !isJsonField) {
374
+ if (!kyselyType.includes("| null")) {
375
+ kyselyType = `${kyselyType} | null`;
376
+ }
377
+ }
378
+ content = `${content}
379
+ ${field}: ${kyselyType};`;
380
+ }
381
+ }
382
+ content = `${content}
383
+ }
384
+
385
+ // Define the database interface
386
+ export interface ${schemaName} {
387
+ ${table}: ${camelCase(table, { pascalCase: true })}Table;
388
+ }
389
+
390
+ // Use these types for inserting, selecting and updating the table
391
+ export type ${camelCase(table, { pascalCase: true })} = Selectable<${camelCase(table, { pascalCase: true })}Table>;
392
+ export type New${camelCase(table, { pascalCase: true })} = Insertable<${camelCase(table, { pascalCase: true })}Table>;
393
+ export type ${camelCase(table, { pascalCase: true })}Update = Updateable<${camelCase(table, { pascalCase: true })}Table>;
394
+ `;
395
+ } else if (destination.type === "ts") {
396
+ const modelType = destination.modelType || "interface";
397
+ const isInterface = modelType === "interface";
398
+ const header = destination.header;
399
+ content = header ? `${header}
400
+
401
+ ` : "";
402
+ content += `// TypeScript ${isInterface ? "interfaces" : "types"} for ${table}`;
403
+ if (enumDeclarations2[table] && enumDeclarations2[table].length > 0) {
404
+ content += "\n\n// Enum declarations";
405
+ for (const enumDecl of enumDeclarations2[table]) {
406
+ content += `
407
+ ${enumDecl}`;
408
+ }
409
+ content += "\n";
410
+ }
411
+ if (isInterface) {
412
+ content += `
413
+ export interface ${camelCase(table, { pascalCase: true })} {`;
414
+ } else {
415
+ content += `
416
+ export type ${camelCase(table, { pascalCase: true })} = {`;
417
+ }
418
+ for (const desc of describes) {
419
+ const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
420
+ const type = getType("table", desc, config, destination, table);
421
+ if (type) {
422
+ content = `${content}
423
+ ${field}: ${type};`;
424
+ }
425
+ }
426
+ content = `${content}
427
+ }
428
+
429
+ `;
430
+ if (isInterface) {
431
+ content += `export interface Insertable${camelCase(table, { pascalCase: true })} {`;
432
+ } else {
433
+ content += `export type Insertable${camelCase(table, { pascalCase: true })} = {`;
434
+ }
435
+ for (const desc of describes) {
436
+ const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
437
+ const type = getType("insertable", desc, config, destination, table);
438
+ if (type) {
439
+ content = `${content}
440
+ ${field}: ${type};`;
441
+ }
442
+ }
443
+ content = `${content}
444
+ }
445
+
446
+ `;
447
+ if (isInterface) {
448
+ content += `export interface Updateable${camelCase(table, { pascalCase: true })} {`;
449
+ } else {
450
+ content += `export type Updateable${camelCase(table, { pascalCase: true })} = {`;
451
+ }
452
+ for (const desc of describes) {
453
+ const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
454
+ const type = getType("updateable", desc, config, destination, table);
455
+ if (type) {
456
+ content = `${content}
457
+ ${field}: ${type};`;
458
+ }
459
+ }
460
+ content = `${content}
461
+ }
462
+
463
+ `;
464
+ if (isInterface) {
465
+ content += `export interface Selectable${camelCase(table, { pascalCase: true })} {`;
466
+ } else {
467
+ content += `export type Selectable${camelCase(table, { pascalCase: true })} = {`;
468
+ }
469
+ for (const desc of describes) {
470
+ const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
471
+ const type = getType("selectable", desc, config, destination, table);
472
+ if (type) {
473
+ content = `${content}
474
+ ${field}: ${type};`;
475
+ }
476
+ }
477
+ content = `${content}
478
+ }
479
+ `;
480
+ } else if (destination.type === "zod") {
481
+ const header = destination.header;
482
+ content = header ? `${header}
483
+
484
+ ` : defaultZodHeader2;
485
+ content += `export const ${table} = z.object({`;
486
+ for (const desc of describes) {
487
+ const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
488
+ const type = getType("table", desc, config, destination, table);
489
+ if (type) {
490
+ content = `${content}
491
+ ${field}: ${type},`;
492
+ }
493
+ }
494
+ content = `${content}
495
+ })
496
+
497
+ export const insertable_${table} = z.object({`;
498
+ for (const desc of describes) {
499
+ const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
500
+ const type = getType("insertable", desc, config, destination, table);
501
+ if (type) {
502
+ content = `${content}
503
+ ${field}: ${type},`;
504
+ }
505
+ }
506
+ content = `${content}
507
+ })
508
+
509
+ export const updateable_${table} = z.object({`;
510
+ for (const desc of describes) {
511
+ const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
512
+ const type = getType("updateable", desc, config, destination, table);
513
+ if (type) {
514
+ content = `${content}
515
+ ${field}: ${type},`;
516
+ }
517
+ }
518
+ content = `${content}
519
+ })
520
+
521
+ export const selectable_${table} = z.object({`;
522
+ for (const desc of describes) {
523
+ const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
524
+ const type = getType("selectable", desc, config, destination, table);
525
+ if (type) {
526
+ content = `${content}
527
+ ${field}: ${type},`;
528
+ }
529
+ }
530
+ content = `${content}
531
+ })
532
+
533
+ export type ${camelCase(`${table}Type`, {
534
+ pascalCase: true
535
+ })} = z.infer<typeof ${table}>
536
+ export type Insertable${camelCase(`${table}Type`, {
537
+ pascalCase: true
538
+ })} = z.infer<typeof insertable_${table}>
539
+ export type Updateable${camelCase(`${table}Type`, {
540
+ pascalCase: true
541
+ })} = z.infer<typeof updateable_${table}>
542
+ export type Selectable${camelCase(`${table}Type`, {
543
+ pascalCase: true
544
+ })} = z.infer<typeof selectable_${table}>
545
+ `;
546
+ }
547
+ return content;
548
+ }
549
+ const defaultKyselyHeader = "import { Generated, ColumnType, Selectable, Insertable, Updateable } from 'kysely';\n\n";
550
+ const defaultZodHeader = "import { z } from 'zod';\n\n";
150
551
  async function generate(config) {
151
552
  let tables = [];
152
553
  let prismaTables = [];
153
554
  let schema = null;
154
- const db = config.origin.type === "mysql" ? knex({
155
- client: "mysql2",
156
- connection: {
157
- host: config.origin.host,
158
- port: config.origin.port,
159
- user: config.origin.user,
160
- password: config.origin.password,
161
- database: config.origin.database,
162
- ssl: config.ssl
163
- }
164
- }) : null;
555
+ let db = null;
556
+ if (config.destinations.length === 0) {
557
+ throw new Error("Empty destinations object.");
558
+ }
559
+ const dryRunOutput = {};
560
+ if (config.origin.type === "mysql") {
561
+ db = knex({
562
+ client: "mysql2",
563
+ connection: {
564
+ host: config.origin.host,
565
+ port: config.origin.port,
566
+ user: config.origin.user,
567
+ password: config.origin.password,
568
+ database: config.origin.database,
569
+ ssl: config.origin.ssl
570
+ }
571
+ });
572
+ } else if (config.origin.type === "postgres") {
573
+ db = knex({
574
+ client: "pg",
575
+ connection: {
576
+ host: config.origin.host,
577
+ port: config.origin.port,
578
+ user: config.origin.user,
579
+ password: config.origin.password,
580
+ database: config.origin.database,
581
+ ssl: config.origin.ssl
582
+ }
583
+ });
584
+ } else if (config.origin.type === "sqlite") {
585
+ db = knex({
586
+ client: "sqlite3",
587
+ connection: {
588
+ filename: config.origin.path
589
+ },
590
+ useNullAsDefault: true
591
+ });
592
+ }
165
593
  const isCamelCase = config.camelCase && config.camelCase === true;
166
594
  if (config.origin.type === "prisma") {
167
595
  const schemaContents = readFileSync(config.origin.path).toString();
168
596
  schema = createPrismaSchemaBuilder(schemaContents);
169
597
  prismaTables = schema.findAllByType("model", {});
170
598
  tables = prismaTables.filter((t) => t !== null).map((table) => table.name);
171
- } else {
599
+ } else if (config.origin.type === "mysql" && db) {
172
600
  const t = await db.raw(
173
601
  "SELECT table_name as table_name FROM information_schema.tables WHERE table_schema = ?",
174
602
  [config.origin.database]
175
603
  );
176
604
  tables = t[0].map((row) => row.table_name).sort();
605
+ } else if (config.origin.type === "postgres" && db) {
606
+ const schema2 = config.origin.schema || "public";
607
+ const t = await db.raw(
608
+ "SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND table_type = ?",
609
+ [schema2, "BASE TABLE"]
610
+ );
611
+ tables = t.rows.map((row) => row.table_name).sort();
612
+ } else if (config.origin.type === "sqlite" && db) {
613
+ const t = await db.raw(
614
+ "SELECT name as table_name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'"
615
+ );
616
+ tables = t.map((row) => row.table_name).sort();
177
617
  }
178
618
  const dests = [];
179
619
  const includedTables = config.tables;
@@ -200,9 +640,94 @@ async function generate(config) {
200
640
  }
201
641
  let describes = [];
202
642
  for (let table of tables) {
203
- if (config.origin.type === "mysql") {
643
+ if (config.origin.type === "mysql" && db) {
204
644
  const d = await db.raw(`SHOW FULL COLUMNS FROM ${table}`);
205
645
  describes = d[0];
646
+ } else if (config.origin.type === "postgres" && db) {
647
+ const schema2 = config.origin.schema || "public";
648
+ const d = await db.raw(
649
+ `
650
+ SELECT
651
+ column_name as "Field",
652
+ column_default as "Default",
653
+ CASE WHEN is_nullable = 'YES' THEN 'YES' ELSE 'NO' END as "Null",
654
+ data_type as "Type",
655
+ CASE
656
+ WHEN column_default LIKE 'nextval(%' THEN 'auto_increment'
657
+ WHEN column_default IS NOT NULL AND (
658
+ column_default LIKE 'now()%' OR
659
+ column_default LIKE 'uuid_generate_v4()%' OR
660
+ column_default LIKE 'gen_random_uuid()%' OR
661
+ column_default LIKE 'current_timestamp%' OR
662
+ column_default LIKE 'current_date%' OR
663
+ column_default LIKE 'current_time%' OR
664
+ column_default LIKE '(%' OR
665
+ column_default LIKE 'array[%' OR
666
+ column_default LIKE 'json_build_%'
667
+ ) THEN 'DEFAULT_GENERATED'
668
+ ELSE ''
669
+ END as "Extra",
670
+ col_description(('"'||$1||'"."'||$2||'"')::regclass::oid, ordinal_position) as "Comment"
671
+ FROM
672
+ information_schema.columns
673
+ WHERE
674
+ table_schema = $1 AND table_name = $2
675
+ ORDER BY
676
+ ordinal_position
677
+ `,
678
+ [schema2, table]
679
+ );
680
+ for (const column of d.rows) {
681
+ if (column.Type === "USER-DEFINED") {
682
+ const enumValues = await db.raw(
683
+ `
684
+ SELECT
685
+ e.enumlabel
686
+ FROM
687
+ pg_type t
688
+ JOIN pg_enum e ON t.oid = e.enumtypid
689
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
690
+ WHERE
691
+ t.typname = (
692
+ SELECT udt_name
693
+ FROM information_schema.columns
694
+ WHERE table_schema = $1
695
+ AND table_name = $2
696
+ AND column_name = $3
697
+ )
698
+ ORDER BY
699
+ e.enumsortorder
700
+ `,
701
+ [schema2, table, column.Field]
702
+ );
703
+ column.EnumOptions = enumValues.rows.map(
704
+ (row) => row.enumlabel
705
+ );
706
+ }
707
+ }
708
+ describes = d.rows;
709
+ } else if (config.origin.type === "sqlite" && db) {
710
+ const d = await db.raw(`PRAGMA table_info(${table})`);
711
+ describes = d.map(
712
+ (row) => {
713
+ let extra = "";
714
+ if (row.dflt_value !== null) {
715
+ if (row.name === "rowid" || row.type.toLowerCase() === "integer primary key") {
716
+ extra = "auto_increment";
717
+ } 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_")) {
718
+ extra = "DEFAULT_GENERATED";
719
+ }
720
+ }
721
+ return {
722
+ Field: row.name,
723
+ Default: row.dflt_value,
724
+ Null: row.notnull === 0 ? "YES" : "NO",
725
+ Type: row.type.toLowerCase(),
726
+ Extra: extra,
727
+ Comment: ""
728
+ };
729
+ }
730
+ );
206
731
  } else {
207
732
  const prismaTable = prismaTables.find((t) => t?.name === table);
208
733
  let enumOptions;
@@ -218,7 +743,7 @@ async function generate(config) {
218
743
  }
219
744
  const parsedDefaultValue = defaultValue !== void 0 && typeof defaultValue !== "object" ? defaultValue.toString().replace(/"/g, "") : null;
220
745
  let fieldType = field.fieldType.toString();
221
- if (!prismaValidTypes.includes(fieldType)) {
746
+ if (!prismaValidTypes.includes(fieldType) && schema) {
222
747
  enumOptions = schema.findAllByType("enum", {
223
748
  name: fieldType
224
749
  })[0]?.enumerators.filter(
@@ -241,82 +766,40 @@ async function generate(config) {
241
766
  });
242
767
  }
243
768
  if (isCamelCase) table = camelCase(table);
244
- let content = `import { z } from 'zod'
245
-
246
- export const ${table} = z.object({`;
247
- for (const desc of describes) {
248
- const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
249
- const type = getType("table", desc, config);
250
- if (type) {
251
- content = `${content}
252
- ${field}: ${type},`;
253
- }
254
- }
255
- content = `${content}
256
- })
257
-
258
- export const insertable_${table} = z.object({`;
259
- for (const desc of describes) {
260
- const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
261
- const type = getType("insertable", desc, config);
262
- if (type) {
263
- content = `${content}
264
- ${field}: ${type},`;
265
- }
266
- }
267
- content = `${content}
268
- })
269
-
270
- export const updateable_${table} = z.object({`;
271
- for (const desc of describes) {
272
- const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
273
- const type = getType("updateable", desc, config);
274
- if (type) {
275
- content = `${content}
276
- ${field}: ${type},`;
277
- }
769
+ if (!config.destinations || config.destinations.length === 0) {
770
+ throw new Error("No destinations specified");
278
771
  }
279
- content = `${content}
280
- })
281
-
282
- export const selectable_${table} = z.object({`;
283
- for (const desc of describes) {
284
- const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
285
- const type = getType("selectable", desc, config);
286
- if (type) {
287
- content = `${content}
288
- ${field}: ${type},`;
772
+ for (const destination of config.destinations) {
773
+ const content = generateContent({
774
+ table,
775
+ describes,
776
+ config,
777
+ destination,
778
+ isCamelCase: isCamelCase === true,
779
+ enumDeclarations,
780
+ defaultZodHeader,
781
+ defaultKyselyHeader
782
+ });
783
+ const suffix = destination.suffix || "";
784
+ const folder = destination.folder || ".";
785
+ const file = suffix !== "" ? `${table}.${suffix}.ts` : `${table}.ts`;
786
+ if (config.dryRun) {
787
+ dryRunOutput[file] = content;
788
+ } else {
789
+ const dest = path.join(folder, file);
790
+ dests.push(dest);
791
+ if (!config.silent) console.log("Created:", dest);
792
+ fs.outputFileSync(dest, content);
289
793
  }
290
794
  }
291
- content = `${content}
292
- })
293
-
294
- export type ${camelCase(`${table}Type`, {
295
- pascalCase: true
296
- })} = z.infer<typeof ${table}>
297
- export type Insertable${camelCase(`${table}Type`, {
298
- pascalCase: true
299
- })} = z.infer<typeof insertable_${table}>
300
- export type Updateable${camelCase(`${table}Type`, {
301
- pascalCase: true
302
- })} = z.infer<typeof updateable_${table}>
303
- export type Selectable${camelCase(`${table}Type`, {
304
- pascalCase: true
305
- })} = z.infer<typeof selectable_${table}>
306
- `;
307
- const dir = config.folder && config.folder !== "" ? config.folder : ".";
308
- const file = config.suffix && config.suffix !== "" ? `${table}.${config.suffix}.ts` : `${table}.ts`;
309
- const dest = path.join(dir, file);
310
- dests.push(dest);
311
- if (!config.silent) console.log("Created:", dest);
312
- fs.outputFileSync(dest, content);
313
- }
314
- if (config.origin.type === "mysql") {
315
- await db.destroy();
316
795
  }
317
- return dests;
796
+ if (db) await db.destroy();
797
+ return config.dryRun ? dryRunOutput : dests;
318
798
  }
319
799
  export {
800
+ defaultKyselyHeader,
801
+ defaultZodHeader,
320
802
  generate,
803
+ generateContent,
321
804
  getType
322
805
  };