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 +87 -9
- package/dist/main.d.ts +13 -17
- package/dist/main.js +85 -61
- package/package.json +11 -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
|
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
276
|
-
|
|
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" &&
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
435
|
+
if (typeMappings.stringTypes.includes(dataType) || typeMappings.dateTypes.includes(dataType)) {
|
|
417
436
|
defaultValueFormatted = `'${Default}'`;
|
|
418
|
-
} else if (typeMappings.booleanTypes.includes(
|
|
437
|
+
} else if (typeMappings.booleanTypes.includes(dataType)) {
|
|
419
438
|
defaultValueFormatted = Default.toLowerCase() === "true" ? "true" : "false";
|
|
420
|
-
} else if (typeMappings.numberTypes.includes(
|
|
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(
|
|
454
|
+
const isDateField = typeMappings.dateTypes.includes(dataType);
|
|
436
455
|
const shouldDateBeOptional = isDateField && (hasDefaultValue || isGenerated) && (op === "table" || op === "selectable");
|
|
437
|
-
const isIdField = typeMappings.numberTypes.includes(
|
|
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.
|
|
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.
|
|
19
|
+
"@mrleebo/prisma-ast": "^0.13.1",
|
|
20
20
|
"@types/fs-extra": "^11.0.4",
|
|
21
|
-
"@types/pg": "^8.
|
|
21
|
+
"@types/pg": "^8.16.0",
|
|
22
22
|
"@types/pluralize": "^0.0.33",
|
|
23
|
-
"camelcase": "^
|
|
24
|
-
"fs-extra": "^11.3.
|
|
23
|
+
"camelcase": "^9.0.0",
|
|
24
|
+
"fs-extra": "^11.3.3",
|
|
25
25
|
"knex": "^3.1.0",
|
|
26
|
-
"mysql2": "^3.
|
|
27
|
-
"pg": "^8.
|
|
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.
|
|
33
|
-
"pkgroll": "^2.
|
|
34
|
-
"typescript": "^5.9.
|
|
35
|
-
"vitest": "^
|
|
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
|
}
|