mutano 3.2.0 → 3.3.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
@@ -180,6 +180,39 @@ export type UpdateableUser = Updateable<User>;
180
180
  | `camelCase` | Convert to camelCase |
181
181
  | `dryRun` | Return content without writing files |
182
182
  | `magicComments` | Enable @zod/@ts/@kysely comments (Obs.: no SQLite support) |
183
+ | `inflection` | Transform model names: `'singular'`, `'plural'`, or `'none'` (default) |
184
+
185
+ ### Inflection
186
+
187
+ Transform table/view names to singular or plural form in generated types:
188
+
189
+ ```typescript
190
+ // Singular inflection - "users" table becomes "User" type
191
+ await generate({
192
+ origin: { /* ... */ },
193
+ destinations: [{ type: 'zod' }],
194
+ inflection: 'singular'
195
+ })
196
+
197
+ // Plural inflection - "user" table becomes "Users" type
198
+ await generate({
199
+ origin: { /* ... */ },
200
+ destinations: [{ type: 'ts' }],
201
+ inflection: 'plural'
202
+ })
203
+ ```
204
+
205
+ **Examples:**
206
+
207
+ | Table Name | `inflection: 'singular'` | `inflection: 'plural'` | `inflection: 'none'` |
208
+ |------------|-------------------------|----------------------|---------------------|
209
+ | `users` | `UserType` | `UsersType` | `UsersType` |
210
+ | `companies` | `CompanyType` | `CompaniesType` | `CompaniesType` |
211
+ | `people` | `PersonType` | `PeopleType` | `PeopleType` |
212
+ | `categories` | `CategoryType` | `CategoriesType` | `CategoriesType` |
213
+ | `user_accounts` | `UserAccountType` | `UserAccountsType` | `UserAccountsType` |
214
+
215
+ Works with all output types (Zod, TypeScript, Kysely) and combines with `camelCase` option.
183
216
 
184
217
  ## Magic Comments
185
218
 
package/dist/main.d.ts CHANGED
@@ -84,6 +84,7 @@ interface Config {
84
84
  magicComments?: boolean;
85
85
  includeViews?: boolean;
86
86
  enumDeclarations?: Record<string, string[]>;
87
+ inflection?: 'singular' | 'plural' | 'none';
87
88
  }
88
89
  interface GenerateContentParams {
89
90
  table: string;
@@ -160,12 +161,8 @@ declare function getType(op: OperationType, desc: Desc, config: Config, destinat
160
161
 
161
162
  /**
162
163
  * Mutano - Database schema to TypeScript/Zod/Kysely converter
163
- * Refactored for better maintainability and modularity
164
164
  */
165
165
 
166
- /**
167
- * Main generate function - orchestrates the entire schema generation process
168
- */
169
166
  declare function generate(config: Config): Promise<Record<string, string>>;
170
167
 
171
168
  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: [
@@ -472,8 +483,9 @@ function generateViewContent({
472
483
  defaultZodHeader
473
484
  }) {
474
485
  let content = "";
486
+ const inflectedView = applyInflection(view, config.inflection);
475
487
  if (destination.type === "kysely") {
476
- const pascalView = camelCase(view, { pascalCase: true });
488
+ const pascalView = camelCase(inflectedView, { pascalCase: true });
477
489
  content += `// Kysely type definitions for ${view} (view)
478
490
 
479
491
  `;
@@ -493,7 +505,7 @@ function generateViewContent({
493
505
  content += `export type Selectable${pascalView}View = Selectable<${pascalView}View>;
494
506
  `;
495
507
  } else if (destination.type === "ts") {
496
- const pascalView = camelCase(view, { pascalCase: true });
508
+ const pascalView = camelCase(inflectedView, { pascalCase: true });
497
509
  content += `// TypeScript interface for ${view} (view - read-only)
498
510
  `;
499
511
  content += `export interface ${pascalView}View {
@@ -523,7 +535,7 @@ function generateViewContent({
523
535
  `;
524
536
  }
525
537
  content += "})\n\n";
526
- const pascalView = camelCase(view, { pascalCase: true });
538
+ const pascalView = camelCase(inflectedView, { pascalCase: true });
527
539
  content += `export type ${camelCase(`${pascalView}ViewType`, {
528
540
  pascalCase: true
529
541
  })} = z.infer<typeof ${snakeView}_view>
@@ -577,7 +589,8 @@ function generateTypeScriptContent({
577
589
  isCamelCase
578
590
  }) {
579
591
  let content = "";
580
- const pascalTable = camelCase(table, { pascalCase: true });
592
+ const inflectedTable = applyInflection(table, config.inflection);
593
+ const pascalTable = camelCase(inflectedTable, { pascalCase: true });
581
594
  content += `// TypeScript interfaces for ${table}
582
595
 
583
596
  `;
@@ -627,7 +640,8 @@ function generateKyselyContent({
627
640
  isCamelCase
628
641
  }) {
629
642
  let content = "";
630
- const pascalTable = camelCase(table, { pascalCase: true });
643
+ const inflectedTable = applyInflection(table, config.inflection);
644
+ const pascalTable = camelCase(inflectedTable, { pascalCase: true });
631
645
  content += `// Kysely type definitions for ${table}
632
646
 
633
647
  `;
@@ -676,6 +690,7 @@ function generateZodContent({
676
690
  content += header;
677
691
  }
678
692
  const snakeTable = toSnakeCase(table);
693
+ const inflectedTable = applyInflection(table, config.inflection);
679
694
  content += `export const ${snakeTable} = z.object({
680
695
  `;
681
696
  for (const desc of describes) {
@@ -720,13 +735,14 @@ function generateZodContent({
720
735
  `;
721
736
  }
722
737
  content += "})\n\n";
723
- content += `export type ${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof ${snakeTable}>
738
+ const pascalInflectedTableType = camelCase(`${inflectedTable}Type`, { pascalCase: true });
739
+ content += `export type ${pascalInflectedTableType} = z.infer<typeof ${snakeTable}>
724
740
  `;
725
- content += `export type Insertable${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof insertable_${snakeTable}>
741
+ content += `export type Insertable${pascalInflectedTableType} = z.infer<typeof insertable_${snakeTable}>
726
742
  `;
727
- content += `export type Updateable${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof updateable_${snakeTable}>
743
+ content += `export type Updateable${pascalInflectedTableType} = z.infer<typeof updateable_${snakeTable}>
728
744
  `;
729
- content += `export type Selectable${camelCase(`${table}Type`, { pascalCase: true })} = z.infer<typeof selectable_${snakeTable}>
745
+ content += `export type Selectable${pascalInflectedTableType} = z.infer<typeof selectable_${snakeTable}>
730
746
  `;
731
747
  return content;
732
748
  }
@@ -913,7 +929,24 @@ function extractPrismaEntities(config) {
913
929
  if (prismaEnum && "name" in prismaEnum && "enumerators" in prismaEnum) {
914
930
  const enumName = prismaEnum.name;
915
931
  const enumerators = prismaEnum.enumerators;
916
- enumDeclarations[enumName] = enumerators.map((e) => {
932
+ const hasEnumIgnore = enumerators.some(
933
+ (item) => item.type === "attribute" && item.name === "ignore" && item.kind === "object"
934
+ );
935
+ if (hasEnumIgnore) {
936
+ continue;
937
+ }
938
+ const filteredEnumValues = enumerators.filter((e) => {
939
+ if (e.type === "attribute") {
940
+ return false;
941
+ }
942
+ if ("attributes" in e && e.attributes) {
943
+ const hasIgnore = e.attributes.some((attr) => attr.name === "ignore");
944
+ if (hasIgnore) {
945
+ return false;
946
+ }
947
+ }
948
+ return true;
949
+ }).map((e) => {
917
950
  if ("attributes" in e && e.attributes) {
918
951
  const mapAttr = e.attributes.find((attr) => attr.name === "map");
919
952
  if (mapAttr && mapAttr.args && mapAttr.args.length > 0) {
@@ -931,6 +964,7 @@ function extractPrismaEntities(config) {
931
964
  }
932
965
  return e.name;
933
966
  });
967
+ enumDeclarations[enumName] = filteredEnumValues;
934
968
  }
935
969
  }
936
970
  return { tables, views, enumDeclarations };
@@ -1138,8 +1172,9 @@ export interface ${schemaName} {
1138
1172
  `;
1139
1173
  const sortedTableEntries = tableContents.map(({ table, content }) => {
1140
1174
  const isView = content.includes("(view");
1141
- const pascalTable = camelCase(table, { pascalCase: true }) + (isView ? "View" : "");
1142
- const tableKey = isCamelCase ? camelCase(table) : table;
1175
+ const inflectedTable = applyInflection(table, config.inflection);
1176
+ const pascalTable = camelCase(inflectedTable, { pascalCase: true }) + (isView ? "View" : "");
1177
+ const tableKey = isCamelCase ? camelCase(inflectedTable) : inflectedTable;
1143
1178
  return { tableKey, pascalTable, isView };
1144
1179
  }).sort((a, b) => a.tableKey.localeCompare(b.tableKey));
1145
1180
  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.0",
4
+ "version": "3.3.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",
@@ -19,11 +19,13 @@
19
19
  "@mrleebo/prisma-ast": "^0.13.0",
20
20
  "@types/fs-extra": "^11.0.4",
21
21
  "@types/pg": "^8.15.5",
22
+ "@types/pluralize": "^0.0.33",
22
23
  "camelcase": "^8.0.0",
23
24
  "fs-extra": "^11.3.2",
24
25
  "knex": "^3.1.0",
25
26
  "mysql2": "^3.14.4",
26
27
  "pg": "^8.16.3",
28
+ "pluralize": "^8.0.0",
27
29
  "sqlite3": "^5.1.7"
28
30
  },
29
31
  "devDependencies": {