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 +33 -0
- package/dist/main.d.ts +1 -4
- package/dist/main.js +47 -12
- package/package.json +3 -1
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
738
|
+
const pascalInflectedTableType = camelCase(`${inflectedTable}Type`, { pascalCase: true });
|
|
739
|
+
content += `export type ${pascalInflectedTableType} = z.infer<typeof ${snakeTable}>
|
|
724
740
|
`;
|
|
725
|
-
content += `export type Insertable${
|
|
741
|
+
content += `export type Insertable${pascalInflectedTableType} = z.infer<typeof insertable_${snakeTable}>
|
|
726
742
|
`;
|
|
727
|
-
content += `export type Updateable${
|
|
743
|
+
content += `export type Updateable${pascalInflectedTableType} = z.infer<typeof updateable_${snakeTable}>
|
|
728
744
|
`;
|
|
729
|
-
content += `export type Selectable${
|
|
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
|
-
|
|
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
|
|
1142
|
-
const
|
|
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.
|
|
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": {
|