mutano 2.3.0 → 2.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 +29 -6
- package/dist/main.d.ts +4 -0
- package/dist/main.js +62 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ CREATE TABLE `user` (
|
|
|
30
30
|
`username` varchar(255) NOT NULL,
|
|
31
31
|
`password` varchar(255) NOT NULL,
|
|
32
32
|
`profile_picture` varchar(255) DEFAULT NULL,
|
|
33
|
-
`metadata` json NOT NULL COMMENT '@ts(Record<string, unknown>)', -- this will override the TypeScript type
|
|
33
|
+
`metadata` json NOT NULL COMMENT '@ts(Record<string, unknown>) @kysely(Record<string, string>)', -- this will override the TypeScript and Kysely type
|
|
34
34
|
`role` enum('admin','user') NOT NULL,
|
|
35
35
|
PRIMARY KEY (`id`)
|
|
36
36
|
);
|
|
@@ -184,7 +184,7 @@ const output = await generate({
|
|
|
184
184
|
destinations: [{
|
|
185
185
|
type: 'zod'
|
|
186
186
|
}],
|
|
187
|
-
dryRun: true // Return content
|
|
187
|
+
dryRun: true // Return content and don't write to files
|
|
188
188
|
})
|
|
189
189
|
|
|
190
190
|
// Output is an object where keys are filenames and values are file content
|
|
@@ -400,7 +400,7 @@ export interface UserTable {
|
|
|
400
400
|
username: string;
|
|
401
401
|
password: string;
|
|
402
402
|
profile_picture: string | null;
|
|
403
|
-
metadata:
|
|
403
|
+
metadata: Record<string, unknown>; // Custom type from @kysely comment
|
|
404
404
|
role: 'admin' | 'user';
|
|
405
405
|
}
|
|
406
406
|
|
|
@@ -486,8 +486,7 @@ export type UserUpdate = Updateable<UserTable>;
|
|
|
486
486
|
"type": "kysely",
|
|
487
487
|
"schemaName": "Database",
|
|
488
488
|
"header": "import { Generated, ColumnType } from 'kysely';\nimport { CustomTypes } from './types';",
|
|
489
|
-
"
|
|
490
|
-
"suffix": "db"
|
|
489
|
+
"outFile": "db.ts"
|
|
491
490
|
}
|
|
492
491
|
],
|
|
493
492
|
"tables": ["user", "log"],
|
|
@@ -513,6 +512,7 @@ export type UserUpdate = Updateable<UserTable>;
|
|
|
513
512
|
| destinations[].header | Custom header to include at the beginning of generated files (e.g., custom imports) |
|
|
514
513
|
| destinations[].folder | Specify the output directory for the generated files |
|
|
515
514
|
| destinations[].suffix | Suffix to the name of a generated file (eg: `user.table.ts`) |
|
|
515
|
+
| destinations[].outFile | (Kysely only) Specify the output file for the generated content. All tables will be written to this file |
|
|
516
516
|
| tables | Filter the tables to include only those specified |
|
|
517
517
|
| ignore | Filter the tables to exclude those specified. If a table name begins and ends with "/", it will be processed as a regular expression |
|
|
518
518
|
| camelCase | Convert all table names and their properties to camelcase. (eg: `profile_picture` becomes `profilePicture`) |
|
|
@@ -641,7 +641,30 @@ export interface User {
|
|
|
641
641
|
}
|
|
642
642
|
```
|
|
643
643
|
|
|
644
|
-
|
|
644
|
+
### @kysely Comments
|
|
645
|
+
|
|
646
|
+
You can use the `@kysely` comment to override the Kysely type for a specific column. This is useful when you want to specify a more precise type for a field.
|
|
647
|
+
|
|
648
|
+
```sql
|
|
649
|
+
CREATE TABLE `user` (
|
|
650
|
+
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
|
651
|
+
`metadata` json NOT NULL COMMENT '@kysely(Record<string, string>)',
|
|
652
|
+
PRIMARY KEY (`id`)
|
|
653
|
+
);
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
This will generate:
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
export interface UserTable {
|
|
660
|
+
id: Generated<number>;
|
|
661
|
+
metadata: Record<string, string>;
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
## Complex TypeScript Types
|
|
666
|
+
|
|
667
|
+
You can use complex TypeScript types in the `@ts`(or `@kysely`) comment:
|
|
645
668
|
|
|
646
669
|
```sql
|
|
647
670
|
CREATE TABLE `product` (
|
package/dist/main.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export declare const extractTypeExpression: (comment: string, prefix: string) => string;
|
|
2
|
+
export declare const extractTSExpression: (comment: string) => string;
|
|
3
|
+
export declare const extractKyselyExpression: (comment: string) => string;
|
|
4
|
+
export declare const extractZodExpression: (comment: string) => string;
|
|
1
5
|
export declare function getType(op: 'table' | 'insertable' | 'updateable' | 'selectable', desc: Desc, config: Config, destination: Destination, tableName?: string): string;
|
|
2
6
|
export interface GenerateContentParams {
|
|
3
7
|
table: string;
|
package/dist/main.js
CHANGED
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
import camelCase from "camelcase";
|
|
8
8
|
import fs from "fs-extra";
|
|
9
9
|
import knex from "knex";
|
|
10
|
-
const
|
|
11
|
-
const start = comment.indexOf(
|
|
10
|
+
const extractTypeExpression = (comment, prefix) => {
|
|
11
|
+
const start = comment.indexOf(prefix);
|
|
12
12
|
if (start === -1) return null;
|
|
13
|
-
const typeLen =
|
|
13
|
+
const typeLen = prefix.length;
|
|
14
14
|
let position = start + typeLen;
|
|
15
15
|
let depth = 1;
|
|
16
16
|
while (position < comment.length && depth > 0) {
|
|
@@ -28,24 +28,9 @@ const extractTSExpression = (comment) => {
|
|
|
28
28
|
}
|
|
29
29
|
return null;
|
|
30
30
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let openParens = 0;
|
|
35
|
-
let position = zodStart + 5;
|
|
36
|
-
while (position < comment.length) {
|
|
37
|
-
if (comment[position] === "(") {
|
|
38
|
-
openParens++;
|
|
39
|
-
} else if (comment[position] === ")") {
|
|
40
|
-
if (openParens === 0) {
|
|
41
|
-
return comment.substring(zodStart + 5, position);
|
|
42
|
-
}
|
|
43
|
-
openParens--;
|
|
44
|
-
}
|
|
45
|
-
position++;
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
31
|
+
const extractTSExpression = (comment) => extractTypeExpression(comment, "@ts(");
|
|
32
|
+
const extractKyselyExpression = (comment) => extractTypeExpression(comment, "@kysely(");
|
|
33
|
+
const extractZodExpression = (comment) => extractTypeExpression(comment, "@zod(");
|
|
49
34
|
const prismaValidTypes = [
|
|
50
35
|
"BigInt",
|
|
51
36
|
"Boolean",
|
|
@@ -77,7 +62,6 @@ const stringTypes = {
|
|
|
77
62
|
"mediumtext",
|
|
78
63
|
"longtext",
|
|
79
64
|
"json",
|
|
80
|
-
"decimal",
|
|
81
65
|
"time",
|
|
82
66
|
"year",
|
|
83
67
|
"char",
|
|
@@ -96,9 +80,7 @@ const stringTypes = {
|
|
|
96
80
|
"timetz",
|
|
97
81
|
"interval",
|
|
98
82
|
"name",
|
|
99
|
-
"citext"
|
|
100
|
-
"numeric",
|
|
101
|
-
"decimal"
|
|
83
|
+
"citext"
|
|
102
84
|
],
|
|
103
85
|
sqlite: [
|
|
104
86
|
"text",
|
|
@@ -111,7 +93,7 @@ const stringTypes = {
|
|
|
111
93
|
"clob",
|
|
112
94
|
"json"
|
|
113
95
|
],
|
|
114
|
-
prisma: ["String", "
|
|
96
|
+
prisma: ["String", "BigInt", "Bytes", "Json"]
|
|
115
97
|
};
|
|
116
98
|
const numberTypes = {
|
|
117
99
|
mysql: ["smallint", "mediumint", "int", "bigint", "float", "double"],
|
|
@@ -119,8 +101,6 @@ const numberTypes = {
|
|
|
119
101
|
"smallint",
|
|
120
102
|
"integer",
|
|
121
103
|
"bigint",
|
|
122
|
-
"decimal",
|
|
123
|
-
"numeric",
|
|
124
104
|
"real",
|
|
125
105
|
"double precision",
|
|
126
106
|
"serial",
|
|
@@ -139,12 +119,16 @@ const numberTypes = {
|
|
|
139
119
|
"real",
|
|
140
120
|
"double",
|
|
141
121
|
"double precision",
|
|
142
|
-
"float"
|
|
143
|
-
"numeric",
|
|
144
|
-
"decimal"
|
|
122
|
+
"float"
|
|
145
123
|
],
|
|
146
124
|
prisma: ["Int", "Float"]
|
|
147
125
|
};
|
|
126
|
+
const decimalTypes = {
|
|
127
|
+
mysql: ["decimal"],
|
|
128
|
+
postgres: ["decimal", "numeric"],
|
|
129
|
+
sqlite: ["numeric", "decimal"],
|
|
130
|
+
prisma: ["Decimal"]
|
|
131
|
+
};
|
|
148
132
|
const booleanTypes = {
|
|
149
133
|
mysql: ["tinyint"],
|
|
150
134
|
postgres: ["boolean", "bool"],
|
|
@@ -176,6 +160,13 @@ function getType(op, desc, config, destination, tableName) {
|
|
|
176
160
|
const isRequiredString = destination.type === "zod" && destination.requiredString === true && op !== "selectable";
|
|
177
161
|
const type = schemaType === "mysql" ? Type.split("(")[0].split(" ")[0] : Type;
|
|
178
162
|
if (isTsDestination || isKyselyDestination) {
|
|
163
|
+
if (isKyselyDestination && config.magicComments) {
|
|
164
|
+
const kyselyOverrideType = extractKyselyExpression(Comment);
|
|
165
|
+
if (kyselyOverrideType) {
|
|
166
|
+
const shouldBeNullable2 = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
|
|
167
|
+
return shouldBeNullable2 ? kyselyOverrideType.includes("| null") ? kyselyOverrideType : `${kyselyOverrideType} | null` : kyselyOverrideType;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
179
170
|
const tsOverrideType = config.magicComments ? extractTSExpression(Comment) : null;
|
|
180
171
|
const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
|
|
181
172
|
if (tsOverrideType) {
|
|
@@ -193,6 +184,12 @@ function getType(op, desc, config, destination, tableName) {
|
|
|
193
184
|
if (booleanTypes[schemaType].includes(type)) {
|
|
194
185
|
return shouldBeNullable ? "boolean | null" : "boolean";
|
|
195
186
|
}
|
|
187
|
+
if (decimalTypes[schemaType].includes(type) || type === "Decimal") {
|
|
188
|
+
if (isKyselyDestination) {
|
|
189
|
+
return shouldBeNullable ? "Decimal | null" : "Decimal";
|
|
190
|
+
}
|
|
191
|
+
return shouldBeNullable ? "string | null" : "string";
|
|
192
|
+
}
|
|
196
193
|
if (schemaType !== "sqlite" && enumTypes[schemaType].includes(type)) {
|
|
197
194
|
const enumType = destination.type === "ts" ? destination.enumType || "union" : "union";
|
|
198
195
|
let enumValues = [];
|
|
@@ -324,6 +321,16 @@ function getType(op, desc, config, destination, tableName) {
|
|
|
324
321
|
if (dateTypes[schemaType].includes(type)) return generateDateLikeField();
|
|
325
322
|
if (stringTypes[schemaType].includes(type)) return generateStringLikeField();
|
|
326
323
|
if (numberTypes[schemaType].includes(type)) return generateNumberLikeField();
|
|
324
|
+
if (decimalTypes[schemaType].includes(type) || type === "Decimal") {
|
|
325
|
+
if (isKyselyDestination) {
|
|
326
|
+
const isNull2 = Null === "YES";
|
|
327
|
+
const hasDefaultValue2 = Default !== null;
|
|
328
|
+
const isGenerated2 = Extra.toLowerCase().includes("auto_increment") || Extra.toLowerCase().includes("default_generated");
|
|
329
|
+
const shouldBeNullable = isNull2 || ["insertable", "updateable"].includes(op) && (hasDefaultValue2 || isGenerated2) || op === "updateable" && !isNull2 && !hasDefaultValue2;
|
|
330
|
+
return shouldBeNullable ? "Decimal | null" : "Decimal";
|
|
331
|
+
}
|
|
332
|
+
return generateStringLikeField();
|
|
333
|
+
}
|
|
327
334
|
if (booleanTypes[schemaType].includes(type)) return generateBooleanLikeField();
|
|
328
335
|
if (schemaType !== "sqlite" && enumTypes[schemaType].includes(type))
|
|
329
336
|
return generateEnumLikeField();
|
|
@@ -359,15 +366,26 @@ export interface ${camelCase(table, { pascalCase: true })} {`;
|
|
|
359
366
|
const isEnum = schemaType !== "sqlite" && enumTypes[schemaType].includes(
|
|
360
367
|
schemaType === "mysql" ? desc.Type.split("(")[0].split(" ")[0] : desc.Type
|
|
361
368
|
);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
kyselyType
|
|
366
|
-
}
|
|
367
|
-
if (isNullable && !isJsonField) {
|
|
368
|
-
if (!kyselyType.includes("| null")) {
|
|
369
|
+
const kyselyOverrideType = config.magicComments ? extractKyselyExpression(desc.Comment) : null;
|
|
370
|
+
if (kyselyOverrideType) {
|
|
371
|
+
kyselyType = kyselyOverrideType;
|
|
372
|
+
if (isNullable && !kyselyType.includes("| null")) {
|
|
369
373
|
kyselyType = `${kyselyType} | null`;
|
|
370
374
|
}
|
|
375
|
+
if (isAutoIncrement || isDefaultGenerated || hasDefaultValue && (isEnum || kyselyType === "string" || kyselyType === "boolean" || kyselyType === "number" || kyselyType === "Decimal" || kyselyType.includes("boolean | null") || kyselyType.includes("string | null") || kyselyType.includes("number | null") || kyselyType.includes("Decimal | null"))) {
|
|
376
|
+
kyselyType = `Generated<${kyselyType}>`;
|
|
377
|
+
}
|
|
378
|
+
} else if (isJsonField) {
|
|
379
|
+
kyselyType = "Json";
|
|
380
|
+
} else {
|
|
381
|
+
if (isNullable && !isJsonField) {
|
|
382
|
+
if (!kyselyType.includes("| null")) {
|
|
383
|
+
kyselyType = `${kyselyType} | null`;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (isAutoIncrement || isDefaultGenerated || hasDefaultValue && (isEnum || kyselyType === "string" || kyselyType === "boolean" || kyselyType === "number" || kyselyType === "Decimal" || kyselyType.includes("boolean | null") || kyselyType.includes("string | null") || kyselyType.includes("number | null") || kyselyType.includes("Decimal | null"))) {
|
|
387
|
+
kyselyType = `Generated<${kyselyType}>`;
|
|
388
|
+
}
|
|
371
389
|
}
|
|
372
390
|
content = `${content}
|
|
373
391
|
${field}: ${kyselyType};`;
|
|
@@ -835,6 +853,8 @@ export type JsonPrimitive = boolean | number | string | null;
|
|
|
835
853
|
|
|
836
854
|
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
|
|
837
855
|
|
|
856
|
+
export type Decimal = ColumnType<string, number | string>
|
|
857
|
+
|
|
838
858
|
`;
|
|
839
859
|
consolidatedContent += "// Table Interfaces\n";
|
|
840
860
|
for (const { content } of tableContents) {
|
|
@@ -889,6 +909,10 @@ export interface ${schemaName} {
|
|
|
889
909
|
export {
|
|
890
910
|
defaultKyselyHeader,
|
|
891
911
|
defaultZodHeader,
|
|
912
|
+
extractKyselyExpression,
|
|
913
|
+
extractTSExpression,
|
|
914
|
+
extractTypeExpression,
|
|
915
|
+
extractZodExpression,
|
|
892
916
|
generate,
|
|
893
917
|
generateContent,
|
|
894
918
|
getType
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mutano",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.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",
|