mutano 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -115,22 +115,19 @@ export type UpdateableUser = Updateable<User>;
115
115
  password: string,
116
116
  database: string,
117
117
  schema?: string, // PostgreSQL only
118
- ssl?: { ca, cert, key },
119
- overrideTypes?: Record<string, string>
118
+ ssl?: { ca, cert, key }
120
119
  }
121
120
 
122
121
  // SQLite
123
122
  {
124
123
  type: 'sqlite',
125
- path: string,
126
- overrideTypes?: Record<string, string>
124
+ path: string
127
125
  }
128
126
 
129
127
  // Prisma
130
128
  {
131
129
  type: 'prisma',
132
- path: string,
133
- overrideTypes?: Record<string, string>
130
+ path: string
134
131
  }
135
132
  ```
136
133
 
@@ -181,6 +178,8 @@ export type UpdateableUser = Updateable<User>;
181
178
  | `dryRun` | Return content without writing files |
182
179
  | `magicComments` | Enable @zod/@ts/@kysely comments (Obs.: no SQLite support) |
183
180
  | `inflection` | Transform model names: `'singular'`, `'plural'`, or `'none'` (default) |
181
+ | `overrideTypes` | Override types globally per destination (see below) |
182
+ | `overrideColumns` | Override specific columns per table (see below) |
184
183
 
185
184
  ### Inflection
186
185
 
@@ -307,24 +306,103 @@ Generated types will only include: `id`, `email`, `name`, and `metadata`
307
306
 
308
307
  ## Type Overrides
309
308
 
310
- Override default types globally in your origin config:
309
+ Override default types globally. Define destination-specific overrides for each output type:
311
310
 
312
311
  ```typescript
313
312
  {
314
313
  origin: {
315
314
  type: 'mysql',
316
315
  // ... connection config
317
- overrideTypes: {
316
+ },
317
+ overrideTypes: {
318
+ zod: {
318
319
  json: 'z.record(z.string())',
319
320
  text: 'z.string().max(1000)',
320
321
  decimal: 'z.number().positive()'
322
+ },
323
+ ts: {
324
+ json: 'Record<string, string>',
325
+ text: 'string',
326
+ decimal: 'number'
327
+ },
328
+ kysely: {
329
+ json: 'Json',
330
+ text: 'string',
331
+ decimal: 'Decimal'
321
332
  }
322
333
  }
323
334
  }
324
335
  ```
325
336
 
337
+ **Single destination with multiple outputs:**
338
+
339
+ ```typescript
340
+ {
341
+ origin: {
342
+ type: 'postgres',
343
+ // ...
344
+ },
345
+ destinations: [
346
+ { type: 'zod', folder: './zod' },
347
+ { type: 'ts', folder: './types' },
348
+ { type: 'kysely', outFile: './db.ts' }
349
+ ],
350
+ overrideTypes: {
351
+ zod: { jsonb: 'z.record(z.string())' },
352
+ ts: { jsonb: 'Record<string, string>' },
353
+ kysely: { jsonb: 'Json' }
354
+ }
355
+ }
356
+ ```
357
+
326
358
  **Common Overrides:**
327
359
  - **MySQL**: `json`, `text`, `decimal`, `enum`
328
360
  - **PostgreSQL**: `jsonb`, `uuid`, `text`, `numeric`
329
361
  - **SQLite**: `json`, `text`, `real`
330
- - **Prisma**: `Json`, `String`, `Decimal`
362
+ - **Prisma**: `Json`, `String`, `Decimal`
363
+
364
+ ### Override Columns
365
+
366
+ Override specific columns from specific tables. Takes priority over magic comments:
367
+
368
+ ```typescript
369
+ {
370
+ origin: {
371
+ type: 'mysql',
372
+ // ... connection config
373
+ },
374
+ destinations: [
375
+ { type: 'zod', folder: './zod' },
376
+ { type: 'ts', folder: './types' },
377
+ { type: 'kysely', outFile: './db.ts' }
378
+ ],
379
+ overrideColumns: {
380
+ zod: {
381
+ users: {
382
+ email: 'z.string().email()',
383
+ metadata: 'z.record(z.unknown())'
384
+ },
385
+ posts: {
386
+ content: 'z.string().max(10000)'
387
+ }
388
+ },
389
+ ts: {
390
+ users: {
391
+ email: 'EmailAddress',
392
+ metadata: 'Record<string, unknown>'
393
+ }
394
+ },
395
+ kysely: {
396
+ users: {
397
+ metadata: 'CustomJsonType'
398
+ }
399
+ }
400
+ }
401
+ }
402
+ ```
403
+
404
+ **Priority order** (highest to lowest):
405
+ 1. `overrideColumns` - Specific column overrides
406
+ 2. Magic comments (`@zod`, `@ts`, `@kysely`) - Column-level comments
407
+ 3. `overrideTypes` - Global type overrides
408
+ 4. Default type mappings
package/dist/main.d.ts CHANGED
@@ -7,6 +7,7 @@ interface Desc {
7
7
  Extra: string;
8
8
  Null: string;
9
9
  Type: string;
10
+ DataType?: string;
10
11
  Comment: string;
11
12
  EnumOptions?: string[];
12
13
  }
@@ -40,9 +41,6 @@ interface Config {
40
41
  origin: {
41
42
  type: 'prisma';
42
43
  path: string;
43
- overrideTypes?: {
44
- [k in PrismaValidTypes]?: string;
45
- };
46
44
  } | {
47
45
  type: 'mysql';
48
46
  host: string;
@@ -50,10 +48,8 @@ interface Config {
50
48
  user: string;
51
49
  password: string;
52
50
  database: string;
53
- overrideTypes?: {
54
- [k in MySQLValidTypes]?: string;
55
- };
56
51
  ssl?: Record<string, any>;
52
+ tinyIntAsBoolean?: boolean;
57
53
  } | {
58
54
  type: 'postgres';
59
55
  host: string;
@@ -62,16 +58,10 @@ interface Config {
62
58
  password: string;
63
59
  database: string;
64
60
  schema?: string;
65
- overrideTypes?: {
66
- [k in PostgresValidTypes]?: string;
67
- };
68
61
  ssl?: Record<string, any>;
69
62
  } | {
70
63
  type: 'sqlite';
71
64
  path: string;
72
- overrideTypes?: {
73
- [k in SQLiteValidTypes]?: string;
74
- };
75
65
  };
76
66
  destinations: Destination[];
77
67
  tables?: string[];
@@ -82,6 +72,16 @@ interface Config {
82
72
  silent?: boolean;
83
73
  dryRun?: boolean;
84
74
  magicComments?: boolean;
75
+ overrideTypes?: {
76
+ zod?: Record<string, string>;
77
+ ts?: Record<string, string>;
78
+ kysely?: Record<string, string>;
79
+ };
80
+ overrideColumns?: {
81
+ zod?: Record<string, Record<string, string>>;
82
+ ts?: Record<string, Record<string, string>>;
83
+ kysely?: Record<string, Record<string, string>>;
84
+ };
85
85
  includeViews?: boolean;
86
86
  enumDeclarations?: Record<string, string[]>;
87
87
  inflection?: 'singular' | 'plural' | 'none';
@@ -104,10 +104,6 @@ interface GenerateViewContentParams {
104
104
  enumDeclarations: Record<string, string[]>;
105
105
  defaultZodHeader: (version: 3 | 4) => string;
106
106
  }
107
- type MySQLValidTypes = 'tinyint' | 'smallint' | 'mediumint' | 'int' | 'bigint' | 'decimal' | 'float' | 'double' | 'bit' | 'char' | 'varchar' | 'binary' | 'varbinary' | 'tinyblob' | 'blob' | 'mediumblob' | 'longblob' | 'tinytext' | 'text' | 'mediumtext' | 'longtext' | 'enum' | 'set' | 'date' | 'time' | 'datetime' | 'timestamp' | 'year' | 'json';
108
- type PostgresValidTypes = 'smallint' | 'integer' | 'bigint' | 'decimal' | 'numeric' | 'real' | 'double precision' | 'smallserial' | 'serial' | 'bigserial' | 'money' | 'character varying' | 'varchar' | 'character' | 'char' | 'text' | 'bytea' | 'timestamp' | 'timestamp with time zone' | 'timestamp without time zone' | 'date' | 'time' | 'time with time zone' | 'time without time zone' | 'interval' | 'boolean' | 'enum' | 'point' | 'line' | 'lseg' | 'box' | 'path' | 'polygon' | 'circle' | 'cidr' | 'inet' | 'macaddr' | 'bit' | 'bit varying' | 'uuid' | 'xml' | 'json' | 'jsonb' | 'int4range' | 'int8range' | 'numrange' | 'tsrange' | 'tstzrange' | 'daterange' | 'name' | 'citext';
109
- type SQLiteValidTypes = 'integer' | 'real' | 'text' | 'blob' | 'numeric' | 'boolean' | 'date' | 'datetime' | 'character' | 'varchar' | 'varying character' | 'nchar' | 'native character' | 'nvarchar' | 'clob' | 'double' | 'double precision' | 'float' | 'int' | 'int2' | 'int8' | 'bigint' | 'unsigned big int' | 'mediumint' | 'tinyint' | 'smallint' | 'decimal' | 'json';
110
- type PrismaValidTypes = 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' | 'Json' | 'Bytes' | 'Unsupported';
111
107
 
112
108
  /**
113
109
  * Constants and default headers for code generation
@@ -157,7 +153,7 @@ type OperationType = 'table' | 'insertable' | 'updateable' | 'selectable';
157
153
  /**
158
154
  * Generate the appropriate type for a database field
159
155
  */
160
- declare function getType(op: OperationType, desc: Desc, config: Config, destination: Destination): string;
156
+ declare function getType(op: OperationType, desc: Desc, config: Config, destination: Destination, entityName?: string): string;
161
157
 
162
158
  /**
163
159
  * Mutano - Database schema to TypeScript/Zod/Kysely converter
package/dist/main.js CHANGED
@@ -43,8 +43,8 @@ function filterViews(views, includedViews, ignoredViews) {
43
43
  }
44
44
  function createEntityList(tables, views) {
45
45
  const allEntities = [
46
- ...tables.map((name) => ({ name, type: "table" })),
47
- ...views.map((name) => ({ name, type: "view" }))
46
+ ...tables.filter((name) => typeof name === "string" && name.length > 0).map((name) => ({ name, type: "table" })),
47
+ ...views.filter((name) => typeof name === "string" && name.length > 0).map((name) => ({ name, type: "view" }))
48
48
  ];
49
49
  return allEntities.sort((a, b) => a.name.localeCompare(b.name));
50
50
  }
@@ -60,7 +60,7 @@ function applyInflection(name, inflection) {
60
60
  }
61
61
 
62
62
  const dateTypes = {
63
- mysql: ["date", "datetime", "timestamp"],
63
+ mysql: ["date", "datetime", "datetime(3)", "timestamp", "timestamp(3)"],
64
64
  postgres: [
65
65
  "timestamp",
66
66
  "timestamp with time zone",
@@ -218,10 +218,17 @@ const hasTableIgnoreDirective = (comment) => {
218
218
  return comment.includes("@@ignore");
219
219
  };
220
220
 
221
- function getType(op, desc, config, destination) {
222
- const { Default, Extra, Null, Type, Comment, EnumOptions } = desc;
221
+ function getType(op, desc, config, destination, entityName) {
222
+ const { Default, Extra, Null, Type, DataType, Comment, EnumOptions } = desc;
223
223
  const schemaType = config.origin.type;
224
224
  const type = schemaType === "prisma" ? Type : Type.toLowerCase();
225
+ let dataType = DataType ? schemaType === "prisma" ? DataType : DataType.toLowerCase() : type;
226
+ const isMySQL = schemaType === "mysql";
227
+ const tinyIntAsBoolean = isMySQL && config.origin.tinyIntAsBoolean !== false;
228
+ const isTinyInt1 = isMySQL && dataType === "tinyint" && Type.toLowerCase().includes("(1)");
229
+ if (tinyIntAsBoolean && isTinyInt1) {
230
+ dataType = "boolean";
231
+ }
225
232
  const isNull = Null === "YES";
226
233
  const hasDefaultValue = Default !== null;
227
234
  const isGenerated = Extra.toLowerCase().includes("auto_increment") || Extra.toLowerCase().includes("default_generated");
@@ -229,28 +236,18 @@ function getType(op, desc, config, destination) {
229
236
  const isKyselyDestination = destination.type === "kysely";
230
237
  const isZodDestination = destination.type === "zod";
231
238
  const typeMappings = getTypeMappings(schemaType);
232
- if (isTsDestination || isKyselyDestination) {
233
- const isJsonField = isJsonType(type);
234
- if (isKyselyDestination && isJsonField) {
235
- if (config.magicComments) {
236
- const kyselyOverrideType = extractKyselyExpression(Comment);
237
- if (kyselyOverrideType) {
238
- return kyselyOverrideType;
239
- }
240
- }
239
+ const destKey = isZodDestination ? "zod" : isTsDestination ? "ts" : "kysely";
240
+ if (entityName && config.overrideColumns) {
241
+ const destOverrides = config.overrideColumns[destKey];
242
+ if (destOverrides && destOverrides[entityName] && destOverrides[entityName][desc.Field]) {
243
+ const columnOverride = destOverrides[entityName][desc.Field];
241
244
  const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
242
- return shouldBeNullable ? "Json | null" : "Json";
243
- }
244
- if (isKyselyDestination && config.magicComments) {
245
- const kyselyOverrideType = extractKyselyExpression(Comment);
246
- if (kyselyOverrideType) {
247
- return kyselyOverrideType;
248
- }
249
- }
250
- if ((isTsDestination || isKyselyDestination) && config.magicComments) {
251
- const tsOverrideType = extractTSExpression(Comment);
252
- if (tsOverrideType) {
253
- return tsOverrideType;
245
+ if (isZodDestination) {
246
+ const nullishOption = destination.nullish;
247
+ const nullableMethod = nullishOption && op !== "selectable" ? "nullish" : "nullable";
248
+ return shouldBeNullable ? `${columnOverride}.${nullableMethod}()` : columnOverride;
249
+ } else {
250
+ return shouldBeNullable ? `${columnOverride} | null` : columnOverride;
254
251
  }
255
252
  }
256
253
  }
@@ -260,9 +257,24 @@ function getType(op, desc, config, destination) {
260
257
  return zodOverrideType;
261
258
  }
262
259
  }
263
- const overrideTypes = config.origin.overrideTypes;
264
- if (overrideTypes && overrideTypes[Type]) {
265
- const overrideType = overrideTypes[Type];
260
+ if (isTsDestination && config.magicComments) {
261
+ const tsOverrideType = extractTSExpression(Comment);
262
+ if (tsOverrideType) {
263
+ return tsOverrideType;
264
+ }
265
+ }
266
+ if (isKyselyDestination && config.magicComments) {
267
+ const kyselyOverrideType = extractKyselyExpression(Comment);
268
+ if (kyselyOverrideType) {
269
+ return kyselyOverrideType;
270
+ }
271
+ const tsOverrideType = extractTSExpression(Comment);
272
+ if (tsOverrideType) {
273
+ return tsOverrideType;
274
+ }
275
+ }
276
+ const overrideType = config.overrideTypes?.[destKey]?.[Type];
277
+ if (overrideType) {
266
278
  const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
267
279
  if (isZodDestination) {
268
280
  const nullishOption = destination.nullish;
@@ -272,12 +284,19 @@ function getType(op, desc, config, destination) {
272
284
  return shouldBeNullable ? `${overrideType} | null` : overrideType;
273
285
  }
274
286
  }
275
- const enumTypesForSchema = typeMappings.enumTypes[schemaType] || [];
276
- const isEnum = enumTypesForSchema.includes(type);
287
+ if (isTsDestination || isKyselyDestination) {
288
+ const isJsonField = isJsonType(dataType);
289
+ if (isKyselyDestination && isJsonField) {
290
+ const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
291
+ return shouldBeNullable ? "Json | null" : "Json";
292
+ }
293
+ }
294
+ const enumTypesForSchema = typeMappings.enumTypes || [];
295
+ const isEnum = enumTypesForSchema.includes(dataType);
277
296
  const isPrismaEnum = schemaType === "prisma" && config.enumDeclarations && config.enumDeclarations[type];
278
297
  if (isEnum || isPrismaEnum) {
279
298
  let enumValues = [];
280
- if (schemaType === "mysql" && type === "enum") {
299
+ if (schemaType === "mysql" && dataType === "enum") {
281
300
  const match = Type.match(enumRegex);
282
301
  if (match) {
283
302
  enumValues = match[1].split(",").map((v) => v.trim().replace(/'/g, ""));
@@ -331,12 +350,12 @@ function getType(op, desc, config, destination) {
331
350
  }
332
351
  }
333
352
  }
334
- return generateStandardType(op, desc, config, destination, typeMappings);
353
+ return generateStandardType(op, desc, config, destination, typeMappings, dataType);
335
354
  }
336
- function generateStandardType(op, desc, config, destination, typeMappings) {
355
+ function generateStandardType(op, desc, config, destination, typeMappings, dataType) {
337
356
  const { Default, Extra, Null, Type } = desc;
338
357
  const schemaType = config.origin.type;
339
- const type = schemaType === "prisma" ? Type : Type.toLowerCase();
358
+ schemaType === "prisma" ? Type : Type.toLowerCase();
340
359
  const isNull = Null === "YES";
341
360
  const hasDefaultValue = Default !== null;
342
361
  const isGenerated = Extra.toLowerCase().includes("auto_increment") || Extra.toLowerCase().includes("default_generated");
@@ -345,7 +364,7 @@ function generateStandardType(op, desc, config, destination, typeMappings) {
345
364
  const shouldBeNullable = isNull;
346
365
  const shouldBeOptional = op === "insertable" && (hasDefaultValue || isGenerated) || op === "updateable";
347
366
  let baseType;
348
- if (typeMappings.dateTypes.includes(type)) {
367
+ if (typeMappings.dateTypes.includes(dataType)) {
349
368
  if (isZodDestination) {
350
369
  const useDateType = destination.useDateType;
351
370
  if (useDateType) {
@@ -356,7 +375,7 @@ function generateStandardType(op, desc, config, destination, typeMappings) {
356
375
  } else {
357
376
  baseType = "Date";
358
377
  }
359
- } else if (typeMappings.bigIntTypes.includes(type)) {
378
+ } else if (typeMappings.bigIntTypes.includes(dataType)) {
360
379
  if (isZodDestination) {
361
380
  baseType = "z.string()";
362
381
  } else if (isKyselyDestination) {
@@ -364,7 +383,7 @@ function generateStandardType(op, desc, config, destination, typeMappings) {
364
383
  } else {
365
384
  baseType = "string";
366
385
  }
367
- } else if (typeMappings.decimalTypes.includes(type)) {
386
+ } else if (typeMappings.decimalTypes.includes(dataType)) {
368
387
  if (isZodDestination) {
369
388
  baseType = "z.string()";
370
389
  if (op !== "selectable") {
@@ -378,13 +397,13 @@ function generateStandardType(op, desc, config, destination, typeMappings) {
378
397
  } else {
379
398
  baseType = "string";
380
399
  }
381
- } else if (typeMappings.numberTypes.includes(type)) {
400
+ } else if (typeMappings.numberTypes.includes(dataType)) {
382
401
  if (isZodDestination) {
383
402
  baseType = "z.number()";
384
403
  } else {
385
404
  baseType = "number";
386
405
  }
387
- } else if (typeMappings.booleanTypes.includes(type)) {
406
+ } else if (typeMappings.booleanTypes.includes(dataType)) {
388
407
  if (isZodDestination) {
389
408
  const useBooleanType = destination.useBooleanType;
390
409
  if (useBooleanType) {
@@ -395,7 +414,7 @@ function generateStandardType(op, desc, config, destination, typeMappings) {
395
414
  } else {
396
415
  baseType = "boolean";
397
416
  }
398
- } else if (typeMappings.stringTypes.includes(type)) {
417
+ } else if (typeMappings.stringTypes.includes(dataType)) {
399
418
  if (isZodDestination) {
400
419
  const useTrim = destination.useTrim;
401
420
  const requiredString = destination.requiredString;
@@ -413,11 +432,11 @@ function generateStandardType(op, desc, config, destination, typeMappings) {
413
432
  const nullableMethod = nullishOption && op !== "selectable" ? "nullish" : "nullable";
414
433
  if ((op === "table" || op === "insertable" || op === "updateable") && hasDefaultValue && Default !== null && !isGenerated) {
415
434
  let defaultValueFormatted = Default;
416
- if (typeMappings.stringTypes.includes(type) || typeMappings.dateTypes.includes(type)) {
435
+ if (typeMappings.stringTypes.includes(dataType) || typeMappings.dateTypes.includes(dataType)) {
417
436
  defaultValueFormatted = `'${Default}'`;
418
- } else if (typeMappings.booleanTypes.includes(type)) {
437
+ } else if (typeMappings.booleanTypes.includes(dataType)) {
419
438
  defaultValueFormatted = Default.toLowerCase() === "true" ? "true" : "false";
420
- } else if (typeMappings.numberTypes.includes(type)) {
439
+ } else if (typeMappings.numberTypes.includes(dataType)) {
421
440
  defaultValueFormatted = Default;
422
441
  } else {
423
442
  defaultValueFormatted = `'${Default}'`;
@@ -432,9 +451,9 @@ function generateStandardType(op, desc, config, destination, typeMappings) {
432
451
  return `${baseType}.default(${defaultValueFormatted})`;
433
452
  }
434
453
  }
435
- const isDateField = typeMappings.dateTypes.includes(type);
454
+ const isDateField = typeMappings.dateTypes.includes(dataType);
436
455
  const shouldDateBeOptional = isDateField && (hasDefaultValue || isGenerated) && (op === "table" || op === "selectable");
437
- const isIdField = typeMappings.numberTypes.includes(type) || typeMappings.bigIntTypes.includes(type) || typeMappings.stringTypes.includes(type);
456
+ const isIdField = typeMappings.numberTypes.includes(dataType) || typeMappings.bigIntTypes.includes(dataType) || typeMappings.stringTypes.includes(dataType);
438
457
  const shouldIdBeOptional = isIdField && isGenerated && (op === "table" || op === "selectable");
439
458
  if (shouldBeNullable && shouldBeOptional) {
440
459
  return `${baseType}.${nullableMethod}()`;
@@ -495,7 +514,7 @@ function generateViewContent({
495
514
  `;
496
515
  for (const desc of describes) {
497
516
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
498
- const fieldType = getType("selectable", desc, config, destination);
517
+ const fieldType = getType("selectable", desc, config, destination, view);
499
518
  content += ` ${fieldName}: ${fieldType};
500
519
  `;
501
520
  }
@@ -512,7 +531,7 @@ function generateViewContent({
512
531
  `;
513
532
  for (const desc of describes) {
514
533
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
515
- const fieldType = getType("selectable", desc, config, destination);
534
+ const fieldType = getType("selectable", desc, config, destination, view);
516
535
  content += ` ${fieldName}: ${fieldType};
517
536
  `;
518
537
  }
@@ -530,7 +549,7 @@ function generateViewContent({
530
549
  `;
531
550
  for (const desc of describes) {
532
551
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
533
- const fieldType = getType("selectable", desc, config, destination);
552
+ const fieldType = getType("selectable", desc, config, destination, view);
534
553
  content += ` ${fieldName}: ${fieldType},
535
554
  `;
536
555
  }
@@ -598,7 +617,7 @@ function generateTypeScriptContent({
598
617
  `;
599
618
  for (const desc of describes) {
600
619
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
601
- const fieldType = getType("table", desc, config, destination);
620
+ const fieldType = getType("table", desc, config, destination, table);
602
621
  content += ` ${fieldName}: ${fieldType};
603
622
  `;
604
623
  }
@@ -607,7 +626,7 @@ function generateTypeScriptContent({
607
626
  `;
608
627
  for (const desc of describes) {
609
628
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
610
- const fieldType = getType("insertable", desc, config, destination);
629
+ const fieldType = getType("insertable", desc, config, destination, table);
611
630
  content += ` ${fieldName}: ${fieldType};
612
631
  `;
613
632
  }
@@ -616,7 +635,7 @@ function generateTypeScriptContent({
616
635
  `;
617
636
  for (const desc of describes) {
618
637
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
619
- const fieldType = getType("updateable", desc, config, destination);
638
+ const fieldType = getType("updateable", desc, config, destination, table);
620
639
  content += ` ${fieldName}: ${fieldType};
621
640
  `;
622
641
  }
@@ -625,7 +644,7 @@ function generateTypeScriptContent({
625
644
  `;
626
645
  for (const desc of describes) {
627
646
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
628
- const fieldType = getType("selectable", desc, config, destination);
647
+ const fieldType = getType("selectable", desc, config, destination, table);
629
648
  content += ` ${fieldName}: ${fieldType};
630
649
  `;
631
650
  }
@@ -651,7 +670,7 @@ function generateKyselyContent({
651
670
  `;
652
671
  for (const desc of describes) {
653
672
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
654
- let fieldType = getType("table", desc, config, destination);
673
+ let fieldType = getType("table", desc, config, destination, table);
655
674
  const hasMagicComment = config.magicComments && (desc.Comment.includes("@kysely(") || desc.Comment.includes("@ts("));
656
675
  if (!hasMagicComment) {
657
676
  const isAutoIncrement = desc.Extra.toLowerCase().includes("auto_increment");
@@ -695,7 +714,7 @@ function generateZodContent({
695
714
  `;
696
715
  for (const desc of describes) {
697
716
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
698
- const fieldType = getType("table", desc, config, destination);
717
+ const fieldType = getType("table", desc, config, destination, table);
699
718
  content += ` ${fieldName}: ${fieldType},
700
719
  `;
701
720
  }
@@ -708,7 +727,7 @@ function generateZodContent({
708
727
  continue;
709
728
  }
710
729
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
711
- const fieldType = getType("insertable", desc, config, destination);
730
+ const fieldType = getType("insertable", desc, config, destination, table);
712
731
  content += ` ${fieldName}: ${fieldType},
713
732
  `;
714
733
  }
@@ -721,7 +740,7 @@ function generateZodContent({
721
740
  continue;
722
741
  }
723
742
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
724
- const fieldType = getType("updateable", desc, config, destination);
743
+ const fieldType = getType("updateable", desc, config, destination, table);
725
744
  content += ` ${fieldName}: ${fieldType},
726
745
  `;
727
746
  }
@@ -730,7 +749,7 @@ function generateZodContent({
730
749
  `;
731
750
  for (const desc of describes) {
732
751
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
733
- const fieldType = getType("selectable", desc, config, destination);
752
+ const fieldType = getType("selectable", desc, config, destination, table);
734
753
  content += ` ${fieldName}: ${fieldType},
735
754
  `;
736
755
  }
@@ -796,7 +815,7 @@ async function extractTables(db, config) {
796
815
  FROM information_schema.tables
797
816
  WHERE table_schema = ? AND table_type = 'BASE TABLE'
798
817
  `, [origin.database]);
799
- return mysqlTables[0].filter((row) => !hasTableIgnoreDirective(row.table_comment || "")).map((row) => row.table_name);
818
+ return mysqlTables[0].filter((row) => !hasTableIgnoreDirective(row.TABLE_COMMENT || row.table_comment || "")).map((row) => row.TABLE_NAME || row.table_name).filter((name) => typeof name === "string" && name.length > 0);
800
819
  case "postgres":
801
820
  const schema = origin.schema || "public";
802
821
  const postgresTables = await db.raw(`
@@ -825,7 +844,7 @@ async function extractViews(db, config) {
825
844
  FROM information_schema.tables
826
845
  WHERE table_schema = ? AND table_type = 'VIEW'
827
846
  `, [origin.database]);
828
- return mysqlViews[0].filter((row) => !hasTableIgnoreDirective(row.table_comment || "")).map((row) => row.table_name);
847
+ return mysqlViews[0].filter((row) => !hasTableIgnoreDirective(row.TABLE_COMMENT || row.table_comment || "")).map((row) => row.TABLE_NAME || row.table_name).filter((name) => typeof name === "string" && name.length > 0);
829
848
  case "postgres":
830
849
  const schema = origin.schema || "public";
831
850
  const postgresViews = await db.raw(`
@@ -855,6 +874,7 @@ async function extractColumnDescriptions(db, config, tableName) {
855
874
  column_default as \`Default\`,
856
875
  extra as \`Extra\`,
857
876
  is_nullable as \`Null\`,
877
+ data_type as \`DataType\`,
858
878
  column_type as \`Type\`,
859
879
  column_comment as \`Comment\`
860
880
  FROM information_schema.columns
@@ -866,6 +886,7 @@ async function extractColumnDescriptions(db, config, tableName) {
866
886
  Default: row.Default,
867
887
  Extra: row.Extra || "",
868
888
  Null: row.Null,
889
+ DataType: row.DataType,
869
890
  Type: row.Type,
870
891
  Comment: row.Comment || ""
871
892
  }));
@@ -939,6 +960,9 @@ function extractPrismaEntities(config) {
939
960
  if (e.type === "attribute") {
940
961
  return false;
941
962
  }
963
+ if (!e.name || typeof e.name !== "string") {
964
+ return false;
965
+ }
942
966
  if ("attributes" in e && e.attributes) {
943
967
  const hasIgnore = e.attributes.some((attr) => attr.name === "ignore");
944
968
  if (hasIgnore) {
@@ -963,7 +987,7 @@ function extractPrismaEntities(config) {
963
987
  }
964
988
  }
965
989
  return e.name;
966
- });
990
+ }).filter((value) => typeof value === "string" && value !== "undefined");
967
991
  enumDeclarations[enumName] = filteredEnumValues;
968
992
  }
969
993
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mutano",
3
3
  "type": "module",
4
- "version": "3.3.0",
4
+ "version": "3.5.0",
5
5
  "description": "Converts Prisma/MySQL/PostgreSQL/SQLite schemas to Zod/TS/Kysely interfaces",
6
6
  "author": "Alisson Cavalcante Agiani <thelinuxlich@gmail.com>",
7
7
  "license": "MIT",
@@ -16,22 +16,22 @@
16
16
  "test": "vitest run"
17
17
  },
18
18
  "dependencies": {
19
- "@mrleebo/prisma-ast": "^0.13.0",
19
+ "@mrleebo/prisma-ast": "^0.13.1",
20
20
  "@types/fs-extra": "^11.0.4",
21
- "@types/pg": "^8.15.5",
21
+ "@types/pg": "^8.16.0",
22
22
  "@types/pluralize": "^0.0.33",
23
- "camelcase": "^8.0.0",
24
- "fs-extra": "^11.3.2",
23
+ "camelcase": "^9.0.0",
24
+ "fs-extra": "^11.3.3",
25
25
  "knex": "^3.1.0",
26
- "mysql2": "^3.14.4",
27
- "pg": "^8.16.3",
26
+ "mysql2": "^3.16.3",
27
+ "pg": "^8.18.0",
28
28
  "pluralize": "^8.0.0",
29
29
  "sqlite3": "^5.1.7"
30
30
  },
31
31
  "devDependencies": {
32
- "@electric-sql/pglite": "^0.3.8",
33
- "pkgroll": "^2.15.4",
34
- "typescript": "^5.9.2",
35
- "vitest": "^3.2.4"
32
+ "@electric-sql/pglite": "^0.3.15",
33
+ "pkgroll": "^2.23.0",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^4.0.18"
36
36
  }
37
37
  }