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 +120 -9
- package/dist/main.d.ts +12 -21
- package/dist/main.js +77 -48
- package/package.json +13 -11
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
|
|
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
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
750
|
+
const pascalInflectedTableType = camelCase(`${inflectedTable}Type`, { pascalCase: true });
|
|
751
|
+
content += `export type ${pascalInflectedTableType} = z.infer<typeof ${snakeTable}>
|
|
724
752
|
`;
|
|
725
|
-
content += `export type Insertable${
|
|
753
|
+
content += `export type Insertable${pascalInflectedTableType} = z.infer<typeof insertable_${snakeTable}>
|
|
726
754
|
`;
|
|
727
|
-
content += `export type Updateable${
|
|
755
|
+
content += `export type Updateable${pascalInflectedTableType} = z.infer<typeof updateable_${snakeTable}>
|
|
728
756
|
`;
|
|
729
|
-
content += `export type Selectable${
|
|
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
|
|
1160
|
-
const
|
|
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.
|
|
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.
|
|
19
|
+
"@mrleebo/prisma-ast": "^0.13.1",
|
|
20
20
|
"@types/fs-extra": "^11.0.4",
|
|
21
|
-
"@types/pg": "^8.
|
|
22
|
-
"
|
|
23
|
-
"
|
|
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.
|
|
26
|
-
"pg": "^8.
|
|
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.
|
|
31
|
-
"pkgroll": "^2.
|
|
32
|
-
"typescript": "^5.9.
|
|
33
|
-
"vitest": "^
|
|
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
|
}
|