mutano 3.2.1 → 3.4.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
 
@@ -180,6 +177,41 @@ export type UpdateableUser = Updateable<User>;
180
177
  | `camelCase` | Convert to camelCase |
181
178
  | `dryRun` | Return content without writing files |
182
179
  | `magicComments` | Enable @zod/@ts/@kysely comments (Obs.: no SQLite support) |
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) |
183
+
184
+ ### Inflection
185
+
186
+ Transform table/view names to singular or plural form in generated types:
187
+
188
+ ```typescript
189
+ // Singular inflection - "users" table becomes "User" type
190
+ await generate({
191
+ origin: { /* ... */ },
192
+ destinations: [{ type: 'zod' }],
193
+ inflection: 'singular'
194
+ })
195
+
196
+ // Plural inflection - "user" table becomes "Users" type
197
+ await generate({
198
+ origin: { /* ... */ },
199
+ destinations: [{ type: 'ts' }],
200
+ inflection: 'plural'
201
+ })
202
+ ```
203
+
204
+ **Examples:**
205
+
206
+ | Table Name | `inflection: 'singular'` | `inflection: 'plural'` | `inflection: 'none'` |
207
+ |------------|-------------------------|----------------------|---------------------|
208
+ | `users` | `UserType` | `UsersType` | `UsersType` |
209
+ | `companies` | `CompanyType` | `CompaniesType` | `CompaniesType` |
210
+ | `people` | `PersonType` | `PeopleType` | `PeopleType` |
211
+ | `categories` | `CategoryType` | `CategoriesType` | `CategoriesType` |
212
+ | `user_accounts` | `UserAccountType` | `UserAccountsType` | `UserAccountsType` |
213
+
214
+ Works with all output types (Zod, TypeScript, Kysely) and combines with `camelCase` option.
183
215
 
184
216
  ## Magic Comments
185
217
 
@@ -274,24 +306,103 @@ Generated types will only include: `id`, `email`, `name`, and `metadata`
274
306
 
275
307
  ## Type Overrides
276
308
 
277
- Override default types globally in your origin config:
309
+ Override default types globally. Define destination-specific overrides for each output type:
278
310
 
279
311
  ```typescript
280
312
  {
281
313
  origin: {
282
314
  type: 'mysql',
283
315
  // ... connection config
284
- overrideTypes: {
316
+ },
317
+ overrideTypes: {
318
+ zod: {
285
319
  json: 'z.record(z.string())',
286
320
  text: 'z.string().max(1000)',
287
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'
288
332
  }
289
333
  }
290
334
  }
291
335
  ```
292
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
+
293
358
  **Common Overrides:**
294
359
  - **MySQL**: `json`, `text`, `decimal`, `enum`
295
360
  - **PostgreSQL**: `jsonb`, `uuid`, `text`, `numeric`
296
361
  - **SQLite**: `json`, `text`, `real`
297
- - **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
@@ -40,9 +40,6 @@ interface Config {
40
40
  origin: {
41
41
  type: 'prisma';
42
42
  path: string;
43
- overrideTypes?: {
44
- [k in PrismaValidTypes]?: string;
45
- };
46
43
  } | {
47
44
  type: 'mysql';
48
45
  host: string;
@@ -50,9 +47,6 @@ interface Config {
50
47
  user: string;
51
48
  password: string;
52
49
  database: string;
53
- overrideTypes?: {
54
- [k in MySQLValidTypes]?: string;
55
- };
56
50
  ssl?: Record<string, any>;
57
51
  } | {
58
52
  type: 'postgres';
@@ -62,16 +56,10 @@ interface Config {
62
56
  password: string;
63
57
  database: string;
64
58
  schema?: string;
65
- overrideTypes?: {
66
- [k in PostgresValidTypes]?: string;
67
- };
68
59
  ssl?: Record<string, any>;
69
60
  } | {
70
61
  type: 'sqlite';
71
62
  path: string;
72
- overrideTypes?: {
73
- [k in SQLiteValidTypes]?: string;
74
- };
75
63
  };
76
64
  destinations: Destination[];
77
65
  tables?: string[];
@@ -82,8 +70,19 @@ interface Config {
82
70
  silent?: boolean;
83
71
  dryRun?: boolean;
84
72
  magicComments?: boolean;
73
+ overrideTypes?: {
74
+ zod?: Record<string, string>;
75
+ ts?: Record<string, string>;
76
+ kysely?: Record<string, string>;
77
+ };
78
+ overrideColumns?: {
79
+ zod?: Record<string, Record<string, string>>;
80
+ ts?: Record<string, Record<string, string>>;
81
+ kysely?: Record<string, Record<string, string>>;
82
+ };
85
83
  includeViews?: boolean;
86
84
  enumDeclarations?: Record<string, string[]>;
85
+ inflection?: 'singular' | 'plural' | 'none';
87
86
  }
88
87
  interface GenerateContentParams {
89
88
  table: string;
@@ -103,10 +102,6 @@ interface GenerateViewContentParams {
103
102
  enumDeclarations: Record<string, string[]>;
104
103
  defaultZodHeader: (version: 3 | 4) => string;
105
104
  }
106
- 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';
107
- 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';
108
- 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';
109
- type PrismaValidTypes = 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' | 'Json' | 'Bytes' | 'Unsupported';
110
105
 
111
106
  /**
112
107
  * Constants and default headers for code generation
@@ -156,16 +151,12 @@ type OperationType = 'table' | 'insertable' | 'updateable' | 'selectable';
156
151
  /**
157
152
  * Generate the appropriate type for a database field
158
153
  */
159
- declare function getType(op: OperationType, desc: Desc, config: Config, destination: Destination): string;
154
+ declare function getType(op: OperationType, desc: Desc, config: Config, destination: Destination, entityName?: string): string;
160
155
 
161
156
  /**
162
157
  * Mutano - Database schema to TypeScript/Zod/Kysely converter
163
- * Refactored for better maintainability and modularity
164
158
  */
165
159
 
166
- /**
167
- * Main generate function - orchestrates the entire schema generation process
168
- */
169
160
  declare function generate(config: Config): Promise<Record<string, string>>;
170
161
 
171
162
  export { defaultKyselyHeader, defaultZodHeader, extractKyselyExpression, extractTSExpression, extractTypeExpression, extractZodExpression, generate, generateContent, generateViewContent, getType };
package/dist/main.js CHANGED
@@ -2,6 +2,7 @@ import * as path from 'node:path';
2
2
  import camelCase from 'camelcase';
3
3
  import { writeFile } from 'node:fs/promises';
4
4
  import { ensureDir } from 'fs-extra/esm';
5
+ import pluralize from 'pluralize';
5
6
  import knex from 'knex';
6
7
  import { readFileSync } from 'node:fs';
7
8
  import { createPrismaSchemaBuilder } from '@mrleebo/prisma-ast';
@@ -48,6 +49,16 @@ function createEntityList(tables, views) {
48
49
  return allEntities.sort((a, b) => a.name.localeCompare(b.name));
49
50
  }
50
51
 
52
+ function applyInflection(name, inflection) {
53
+ if (inflection === "singular") {
54
+ return pluralize.singular(name);
55
+ }
56
+ if (inflection === "plural") {
57
+ return pluralize.plural(name);
58
+ }
59
+ return name;
60
+ }
61
+
51
62
  const dateTypes = {
52
63
  mysql: ["date", "datetime", "timestamp"],
53
64
  postgres: [
@@ -207,7 +218,7 @@ const hasTableIgnoreDirective = (comment) => {
207
218
  return comment.includes("@@ignore");
208
219
  };
209
220
 
210
- function getType(op, desc, config, destination) {
221
+ function getType(op, desc, config, destination, entityName) {
211
222
  const { Default, Extra, Null, Type, Comment, EnumOptions } = desc;
212
223
  const schemaType = config.origin.type;
213
224
  const type = schemaType === "prisma" ? Type : Type.toLowerCase();
@@ -218,28 +229,18 @@ function getType(op, desc, config, destination) {
218
229
  const isKyselyDestination = destination.type === "kysely";
219
230
  const isZodDestination = destination.type === "zod";
220
231
  const typeMappings = getTypeMappings(schemaType);
221
- if (isTsDestination || isKyselyDestination) {
222
- const isJsonField = isJsonType(type);
223
- if (isKyselyDestination && isJsonField) {
224
- if (config.magicComments) {
225
- const kyselyOverrideType = extractKyselyExpression(Comment);
226
- if (kyselyOverrideType) {
227
- return kyselyOverrideType;
228
- }
229
- }
232
+ const destKey = isZodDestination ? "zod" : isTsDestination ? "ts" : "kysely";
233
+ if (entityName && config.overrideColumns) {
234
+ const destOverrides = config.overrideColumns[destKey];
235
+ if (destOverrides && destOverrides[entityName] && destOverrides[entityName][desc.Field]) {
236
+ const columnOverride = destOverrides[entityName][desc.Field];
230
237
  const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
231
- return shouldBeNullable ? "Json | null" : "Json";
232
- }
233
- if (isKyselyDestination && config.magicComments) {
234
- const kyselyOverrideType = extractKyselyExpression(Comment);
235
- if (kyselyOverrideType) {
236
- return kyselyOverrideType;
237
- }
238
- }
239
- if ((isTsDestination || isKyselyDestination) && config.magicComments) {
240
- const tsOverrideType = extractTSExpression(Comment);
241
- if (tsOverrideType) {
242
- return tsOverrideType;
238
+ if (isZodDestination) {
239
+ const nullishOption = destination.nullish;
240
+ const nullableMethod = nullishOption && op !== "selectable" ? "nullish" : "nullable";
241
+ return shouldBeNullable ? `${columnOverride}.${nullableMethod}()` : columnOverride;
242
+ } else {
243
+ return shouldBeNullable ? `${columnOverride} | null` : columnOverride;
243
244
  }
244
245
  }
245
246
  }
@@ -249,9 +250,24 @@ function getType(op, desc, config, destination) {
249
250
  return zodOverrideType;
250
251
  }
251
252
  }
252
- const overrideTypes = config.origin.overrideTypes;
253
- if (overrideTypes && overrideTypes[Type]) {
254
- const overrideType = overrideTypes[Type];
253
+ if (isTsDestination && config.magicComments) {
254
+ const tsOverrideType = extractTSExpression(Comment);
255
+ if (tsOverrideType) {
256
+ return tsOverrideType;
257
+ }
258
+ }
259
+ if (isKyselyDestination && config.magicComments) {
260
+ const kyselyOverrideType = extractKyselyExpression(Comment);
261
+ if (kyselyOverrideType) {
262
+ return kyselyOverrideType;
263
+ }
264
+ const tsOverrideType = extractTSExpression(Comment);
265
+ if (tsOverrideType) {
266
+ return tsOverrideType;
267
+ }
268
+ }
269
+ const overrideType = config.overrideTypes?.[destKey]?.[Type];
270
+ if (overrideType) {
255
271
  const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
256
272
  if (isZodDestination) {
257
273
  const nullishOption = destination.nullish;
@@ -261,6 +277,13 @@ function getType(op, desc, config, destination) {
261
277
  return shouldBeNullable ? `${overrideType} | null` : overrideType;
262
278
  }
263
279
  }
280
+ if (isTsDestination || isKyselyDestination) {
281
+ const isJsonField = isJsonType(type);
282
+ if (isKyselyDestination && isJsonField) {
283
+ const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
284
+ return shouldBeNullable ? "Json | null" : "Json";
285
+ }
286
+ }
264
287
  const enumTypesForSchema = typeMappings.enumTypes[schemaType] || [];
265
288
  const isEnum = enumTypesForSchema.includes(type);
266
289
  const isPrismaEnum = schemaType === "prisma" && config.enumDeclarations && config.enumDeclarations[type];
@@ -472,8 +495,9 @@ function generateViewContent({
472
495
  defaultZodHeader
473
496
  }) {
474
497
  let content = "";
498
+ const inflectedView = applyInflection(view, config.inflection);
475
499
  if (destination.type === "kysely") {
476
- const pascalView = camelCase(view, { pascalCase: true });
500
+ const pascalView = camelCase(inflectedView, { pascalCase: true });
477
501
  content += `// Kysely type definitions for ${view} (view)
478
502
 
479
503
  `;
@@ -483,7 +507,7 @@ function generateViewContent({
483
507
  `;
484
508
  for (const desc of describes) {
485
509
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
486
- const fieldType = getType("selectable", desc, config, destination);
510
+ const fieldType = getType("selectable", desc, config, destination, view);
487
511
  content += ` ${fieldName}: ${fieldType};
488
512
  `;
489
513
  }
@@ -493,14 +517,14 @@ function generateViewContent({
493
517
  content += `export type Selectable${pascalView}View = Selectable<${pascalView}View>;
494
518
  `;
495
519
  } else if (destination.type === "ts") {
496
- const pascalView = camelCase(view, { pascalCase: true });
520
+ const pascalView = camelCase(inflectedView, { pascalCase: true });
497
521
  content += `// TypeScript interface for ${view} (view - read-only)
498
522
  `;
499
523
  content += `export interface ${pascalView}View {
500
524
  `;
501
525
  for (const desc of describes) {
502
526
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
503
- const fieldType = getType("selectable", desc, config, destination);
527
+ const fieldType = getType("selectable", desc, config, destination, view);
504
528
  content += ` ${fieldName}: ${fieldType};
505
529
  `;
506
530
  }
@@ -518,12 +542,12 @@ function generateViewContent({
518
542
  `;
519
543
  for (const desc of describes) {
520
544
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
521
- const fieldType = getType("selectable", desc, config, destination);
545
+ const fieldType = getType("selectable", desc, config, destination, view);
522
546
  content += ` ${fieldName}: ${fieldType},
523
547
  `;
524
548
  }
525
549
  content += "})\n\n";
526
- const pascalView = camelCase(view, { pascalCase: true });
550
+ const pascalView = camelCase(inflectedView, { pascalCase: true });
527
551
  content += `export type ${camelCase(`${pascalView}ViewType`, {
528
552
  pascalCase: true
529
553
  })} = z.infer<typeof ${snakeView}_view>
@@ -577,7 +601,8 @@ function generateTypeScriptContent({
577
601
  isCamelCase
578
602
  }) {
579
603
  let content = "";
580
- const pascalTable = camelCase(table, { pascalCase: true });
604
+ const inflectedTable = applyInflection(table, config.inflection);
605
+ const pascalTable = camelCase(inflectedTable, { pascalCase: true });
581
606
  content += `// TypeScript interfaces for ${table}
582
607
 
583
608
  `;
@@ -585,7 +610,7 @@ function generateTypeScriptContent({
585
610
  `;
586
611
  for (const desc of describes) {
587
612
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
588
- const fieldType = getType("table", desc, config, destination);
613
+ const fieldType = getType("table", desc, config, destination, table);
589
614
  content += ` ${fieldName}: ${fieldType};
590
615
  `;
591
616
  }
@@ -594,7 +619,7 @@ function generateTypeScriptContent({
594
619
  `;
595
620
  for (const desc of describes) {
596
621
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
597
- const fieldType = getType("insertable", desc, config, destination);
622
+ const fieldType = getType("insertable", desc, config, destination, table);
598
623
  content += ` ${fieldName}: ${fieldType};
599
624
  `;
600
625
  }
@@ -603,7 +628,7 @@ function generateTypeScriptContent({
603
628
  `;
604
629
  for (const desc of describes) {
605
630
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
606
- const fieldType = getType("updateable", desc, config, destination);
631
+ const fieldType = getType("updateable", desc, config, destination, table);
607
632
  content += ` ${fieldName}: ${fieldType};
608
633
  `;
609
634
  }
@@ -612,7 +637,7 @@ function generateTypeScriptContent({
612
637
  `;
613
638
  for (const desc of describes) {
614
639
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
615
- const fieldType = getType("selectable", desc, config, destination);
640
+ const fieldType = getType("selectable", desc, config, destination, table);
616
641
  content += ` ${fieldName}: ${fieldType};
617
642
  `;
618
643
  }
@@ -627,7 +652,8 @@ function generateKyselyContent({
627
652
  isCamelCase
628
653
  }) {
629
654
  let content = "";
630
- const pascalTable = camelCase(table, { pascalCase: true });
655
+ const inflectedTable = applyInflection(table, config.inflection);
656
+ const pascalTable = camelCase(inflectedTable, { pascalCase: true });
631
657
  content += `// Kysely type definitions for ${table}
632
658
 
633
659
  `;
@@ -637,7 +663,7 @@ function generateKyselyContent({
637
663
  `;
638
664
  for (const desc of describes) {
639
665
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
640
- let fieldType = getType("table", desc, config, destination);
666
+ let fieldType = getType("table", desc, config, destination, table);
641
667
  const hasMagicComment = config.magicComments && (desc.Comment.includes("@kysely(") || desc.Comment.includes("@ts("));
642
668
  if (!hasMagicComment) {
643
669
  const isAutoIncrement = desc.Extra.toLowerCase().includes("auto_increment");
@@ -676,11 +702,12 @@ function generateZodContent({
676
702
  content += header;
677
703
  }
678
704
  const snakeTable = toSnakeCase(table);
705
+ const inflectedTable = applyInflection(table, config.inflection);
679
706
  content += `export const ${snakeTable} = z.object({
680
707
  `;
681
708
  for (const desc of describes) {
682
709
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
683
- const fieldType = getType("table", desc, config, destination);
710
+ const fieldType = getType("table", desc, config, destination, table);
684
711
  content += ` ${fieldName}: ${fieldType},
685
712
  `;
686
713
  }
@@ -693,7 +720,7 @@ function generateZodContent({
693
720
  continue;
694
721
  }
695
722
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
696
- const fieldType = getType("insertable", desc, config, destination);
723
+ const fieldType = getType("insertable", desc, config, destination, table);
697
724
  content += ` ${fieldName}: ${fieldType},
698
725
  `;
699
726
  }
@@ -706,7 +733,7 @@ function generateZodContent({
706
733
  continue;
707
734
  }
708
735
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
709
- const fieldType = getType("updateable", desc, config, destination);
736
+ const fieldType = getType("updateable", desc, config, destination, table);
710
737
  content += ` ${fieldName}: ${fieldType},
711
738
  `;
712
739
  }
@@ -715,18 +742,19 @@ function generateZodContent({
715
742
  `;
716
743
  for (const desc of describes) {
717
744
  const fieldName = isCamelCase ? camelCase(desc.Field) : desc.Field;
718
- const fieldType = getType("selectable", desc, config, destination);
745
+ const fieldType = getType("selectable", desc, config, destination, table);
719
746
  content += ` ${fieldName}: ${fieldType},
720
747
  `;
721
748
  }
722
749
  content += "})\n\n";
723
- content += `export type ${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof ${snakeTable}>
750
+ const pascalInflectedTableType = camelCase(`${inflectedTable}Type`, { pascalCase: true });
751
+ content += `export type ${pascalInflectedTableType} = z.infer<typeof ${snakeTable}>
724
752
  `;
725
- content += `export type Insertable${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof insertable_${snakeTable}>
753
+ content += `export type Insertable${pascalInflectedTableType} = z.infer<typeof insertable_${snakeTable}>
726
754
  `;
727
- content += `export type Updateable${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof updateable_${snakeTable}>
755
+ content += `export type Updateable${pascalInflectedTableType} = z.infer<typeof updateable_${snakeTable}>
728
756
  `;
729
- content += `export type Selectable${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof selectable_${snakeTable}>
757
+ content += `export type Selectable${pascalInflectedTableType} = z.infer<typeof selectable_${snakeTable}>
730
758
  `;
731
759
  return content;
732
760
  }
@@ -1156,8 +1184,9 @@ export interface ${schemaName} {
1156
1184
  `;
1157
1185
  const sortedTableEntries = tableContents.map(({ table, content }) => {
1158
1186
  const isView = content.includes("(view");
1159
- const pascalTable = camelCase(table, { pascalCase: true }) + (isView ? "View" : "");
1160
- const tableKey = isCamelCase ? camelCase(table) : table;
1187
+ const inflectedTable = applyInflection(table, config.inflection);
1188
+ const pascalTable = camelCase(inflectedTable, { pascalCase: true }) + (isView ? "View" : "");
1189
+ const tableKey = isCamelCase ? camelCase(inflectedTable) : inflectedTable;
1161
1190
  return { tableKey, pascalTable, isView };
1162
1191
  }).sort((a, b) => a.tableKey.localeCompare(b.tableKey));
1163
1192
  for (const { tableKey, pascalTable } of sortedTableEntries) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mutano",
3
3
  "type": "module",
4
- "version": "3.2.1",
4
+ "version": "3.4.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,20 +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",
22
- "camelcase": "^8.0.0",
23
- "fs-extra": "^11.3.2",
21
+ "@types/pg": "^8.16.0",
22
+ "@types/pluralize": "^0.0.33",
23
+ "camelcase": "^9.0.0",
24
+ "fs-extra": "^11.3.3",
24
25
  "knex": "^3.1.0",
25
- "mysql2": "^3.14.4",
26
- "pg": "^8.16.3",
26
+ "mysql2": "^3.16.3",
27
+ "pg": "^8.18.0",
28
+ "pluralize": "^8.0.0",
27
29
  "sqlite3": "^5.1.7"
28
30
  },
29
31
  "devDependencies": {
30
- "@electric-sql/pglite": "^0.3.8",
31
- "pkgroll": "^2.15.4",
32
- "typescript": "^5.9.2",
33
- "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"
34
36
  }
35
37
  }