mutano 1.0.8 → 1.1.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 +150 -8
- package/dist/main.js +25 -50
- package/package.json +11 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mutano
|
|
2
2
|
|
|
3
|
-
Converts Prisma/MySQL schemas to Zod interfaces
|
|
3
|
+
Converts Prisma/MySQL/PostgreSQL/SQLite schemas to Zod interfaces
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -17,7 +17,7 @@ Create user table:
|
|
|
17
17
|
```sql
|
|
18
18
|
CREATE TABLE `user` (
|
|
19
19
|
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
|
20
|
-
`name` varchar(255) NOT NULL COMMENT '@zod(z.string().min(10).max(255))', -- this will override the type
|
|
20
|
+
`name` varchar(255) NOT NULL COMMENT '@zod(z.string().min(10).max(255))', -- this will override the type
|
|
21
21
|
`username` varchar(255) NOT NULL,
|
|
22
22
|
`password` varchar(255) NOT NULL,
|
|
23
23
|
`profile_picture` varchar(255) DEFAULT NULL,
|
|
@@ -27,17 +27,59 @@ CREATE TABLE `user` (
|
|
|
27
27
|
```
|
|
28
28
|
Use the mutano API:
|
|
29
29
|
|
|
30
|
+
### MySQL Example
|
|
31
|
+
|
|
30
32
|
```typescript
|
|
31
33
|
import { generate } from 'mutano'
|
|
32
34
|
|
|
33
35
|
await generate({
|
|
34
|
-
origin: {
|
|
36
|
+
origin: {
|
|
35
37
|
type: 'mysql',
|
|
36
38
|
host: '127.0.0.1',
|
|
37
39
|
port: 3306,
|
|
38
40
|
user: 'root',
|
|
39
41
|
password: 'secret',
|
|
40
|
-
database: 'myapp',
|
|
42
|
+
database: 'myapp',
|
|
43
|
+
overrideTypes: {
|
|
44
|
+
json: 'z.record(z.string())'
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### PostgreSQL Example
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { generate } from 'mutano'
|
|
54
|
+
|
|
55
|
+
await generate({
|
|
56
|
+
origin: {
|
|
57
|
+
type: 'postgres',
|
|
58
|
+
host: '127.0.0.1',
|
|
59
|
+
port: 5432,
|
|
60
|
+
user: 'postgres',
|
|
61
|
+
password: 'secret',
|
|
62
|
+
database: 'myapp',
|
|
63
|
+
schema: 'public', // optional, defaults to 'public'
|
|
64
|
+
overrideTypes: {
|
|
65
|
+
jsonb: 'z.record(z.string())'
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### SQLite Example
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { generate } from 'mutano'
|
|
75
|
+
|
|
76
|
+
await generate({
|
|
77
|
+
origin: {
|
|
78
|
+
type: 'sqlite',
|
|
79
|
+
path: './myapp.db',
|
|
80
|
+
overrideTypes: {
|
|
81
|
+
json: 'z.record(z.string())'
|
|
82
|
+
}
|
|
41
83
|
},
|
|
42
84
|
})
|
|
43
85
|
```
|
|
@@ -98,14 +140,42 @@ export type SelectableUserType = z.infer<typeof selectable_user>
|
|
|
98
140
|
"user": "root",
|
|
99
141
|
"password": "secret",
|
|
100
142
|
"database": "myapp",
|
|
143
|
+
"overrideTypes": {
|
|
144
|
+
"json": "z.record(z.string())"
|
|
145
|
+
},
|
|
101
146
|
"ssl": {
|
|
102
147
|
"ca": "path/to/ca.pem",
|
|
103
148
|
"cert": "path/to/cert.pem",
|
|
104
149
|
"key": "path/to/key.pem"
|
|
105
150
|
},
|
|
106
151
|
} | {
|
|
107
|
-
"type": "
|
|
108
|
-
"
|
|
152
|
+
"type": "postgres",
|
|
153
|
+
"host": "127.0.0.1",
|
|
154
|
+
"port": 5432,
|
|
155
|
+
"user": "postgres",
|
|
156
|
+
"password": "secret",
|
|
157
|
+
"database": "myapp",
|
|
158
|
+
"schema": "public",
|
|
159
|
+
"overrideTypes": {
|
|
160
|
+
"jsonb": "z.record(z.string())"
|
|
161
|
+
},
|
|
162
|
+
"ssl": {
|
|
163
|
+
"ca": "path/to/ca.pem",
|
|
164
|
+
"cert": "path/to/cert.pem",
|
|
165
|
+
"key": "path/to/key.pem"
|
|
166
|
+
},
|
|
167
|
+
} | {
|
|
168
|
+
"type": "sqlite",
|
|
169
|
+
"path": "path/to/database.db",
|
|
170
|
+
"overrideTypes": {
|
|
171
|
+
"json": "z.record(z.string())"
|
|
172
|
+
}
|
|
173
|
+
} | {
|
|
174
|
+
"type": "prisma",
|
|
175
|
+
"path": "path/to/schema.prisma",
|
|
176
|
+
"overrideTypes": {
|
|
177
|
+
"Json": "z.record(z.string())"
|
|
178
|
+
}
|
|
109
179
|
},
|
|
110
180
|
"tables": ["user", "log"],
|
|
111
181
|
"ignore": ["log", "/^temp/"],
|
|
@@ -136,5 +206,77 @@ export type SelectableUserType = z.infer<typeof selectable_user>
|
|
|
136
206
|
| useDateType | Use a specialized Zod type for date-like fields instead of string
|
|
137
207
|
| useTrim | Use `z.string().trim()` instead of `z.string()` |
|
|
138
208
|
| silent | Don't log anything to the console |
|
|
139
|
-
|
|
|
140
|
-
|
|
209
|
+
| magicComments | Use @zod comment to override entire type (unsupported by SQLite) |
|
|
210
|
+
|
|
211
|
+
## overrideTypes
|
|
212
|
+
|
|
213
|
+
You can override the default Zod type for a specific column type. This is specific to each database type and is placed inside the origin object. Each database type has its own set of valid types that can be overridden:
|
|
214
|
+
|
|
215
|
+
### MySQL overrideTypes
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"origin": {
|
|
220
|
+
"type": "mysql",
|
|
221
|
+
"host": "127.0.0.1",
|
|
222
|
+
"port": 3306,
|
|
223
|
+
"user": "root",
|
|
224
|
+
"password": "secret",
|
|
225
|
+
"database": "myapp",
|
|
226
|
+
"overrideTypes": {
|
|
227
|
+
"json": "z.record(z.string())",
|
|
228
|
+
"text": "z.string().max(1000)"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### PostgreSQL overrideTypes
|
|
235
|
+
|
|
236
|
+
```json
|
|
237
|
+
{
|
|
238
|
+
"origin": {
|
|
239
|
+
"type": "postgres",
|
|
240
|
+
"host": "127.0.0.1",
|
|
241
|
+
"port": 5432,
|
|
242
|
+
"user": "postgres",
|
|
243
|
+
"password": "secret",
|
|
244
|
+
"database": "myapp",
|
|
245
|
+
"schema": "public",
|
|
246
|
+
"overrideTypes": {
|
|
247
|
+
"jsonb": "z.record(z.string())",
|
|
248
|
+
"uuid": "z.string().uuid()"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### SQLite overrideTypes
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"origin": {
|
|
259
|
+
"type": "sqlite",
|
|
260
|
+
"path": "./myapp.db",
|
|
261
|
+
"overrideTypes": {
|
|
262
|
+
"json": "z.record(z.string())",
|
|
263
|
+
"text": "z.string().max(1000)"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Prisma overrideTypes
|
|
270
|
+
|
|
271
|
+
```json
|
|
272
|
+
{
|
|
273
|
+
"origin": {
|
|
274
|
+
"type": "prisma",
|
|
275
|
+
"path": "./schema.prisma",
|
|
276
|
+
"overrideTypes": {
|
|
277
|
+
"Json": "z.record(z.string())",
|
|
278
|
+
"String": "z.string().min(1)"
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
package/dist/main.js
CHANGED
|
@@ -8,8 +8,7 @@ import fs from "fs-extra";
|
|
|
8
8
|
import knex from "knex";
|
|
9
9
|
function extractZodExpression(comment) {
|
|
10
10
|
const zodStart = comment.indexOf("@zod(");
|
|
11
|
-
if (zodStart === -1)
|
|
12
|
-
return null;
|
|
11
|
+
if (zodStart === -1) return null;
|
|
13
12
|
let openParens = 0;
|
|
14
13
|
let position = zodStart + 5;
|
|
15
14
|
while (position < comment.length) {
|
|
@@ -93,80 +92,59 @@ function getType(op, desc, config) {
|
|
|
93
92
|
const typeOverride = zodOverrideType ?? config.overrideTypes?.[type];
|
|
94
93
|
const generateDateLikeField = () => {
|
|
95
94
|
const field = typeOverride ? [typeOverride] : dateField;
|
|
96
|
-
if (isNull && !typeOverride)
|
|
97
|
-
field.push(nullable);
|
|
95
|
+
if (isNull && !typeOverride) field.push(nullable);
|
|
98
96
|
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
99
97
|
field.push(optional);
|
|
100
|
-
if (hasDefaultValue && !isGenerated)
|
|
101
|
-
|
|
102
|
-
if (isUpdateableFormat)
|
|
103
|
-
field.push(optional);
|
|
98
|
+
if (hasDefaultValue && !isGenerated) field.push(`default('${Default}')`);
|
|
99
|
+
if (isUpdateableFormat) field.push(optional);
|
|
104
100
|
return field.join(".");
|
|
105
101
|
};
|
|
106
102
|
const generateStringLikeField = () => {
|
|
107
103
|
const field = typeOverride ? [typeOverride] : string;
|
|
108
|
-
if (isNull && !typeOverride)
|
|
109
|
-
field.push(nullable);
|
|
104
|
+
if (isNull && !typeOverride) field.push(nullable);
|
|
110
105
|
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
111
106
|
field.push(optional);
|
|
112
|
-
else if (isRequiredString && !typeOverride)
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
field.push(`default('${Default}')`);
|
|
116
|
-
if (isUpdateableFormat)
|
|
117
|
-
field.push(optional);
|
|
107
|
+
else if (isRequiredString && !typeOverride) field.push(min1);
|
|
108
|
+
if (hasDefaultValue && !isGenerated) field.push(`default('${Default}')`);
|
|
109
|
+
if (isUpdateableFormat) field.push(optional);
|
|
118
110
|
return field.join(".");
|
|
119
111
|
};
|
|
120
112
|
const generateBooleanLikeField = () => {
|
|
121
113
|
const field = typeOverride ? [typeOverride] : boolean;
|
|
122
|
-
if (isNull && !typeOverride)
|
|
123
|
-
field.push(nullable);
|
|
114
|
+
if (isNull && !typeOverride) field.push(nullable);
|
|
124
115
|
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
125
116
|
field.push(optional);
|
|
126
117
|
if (hasDefaultValue && !isGenerated)
|
|
127
118
|
field.push(`default(${Boolean(+Default)})`);
|
|
128
|
-
if (isUpdateableFormat)
|
|
129
|
-
field.push(optional);
|
|
119
|
+
if (isUpdateableFormat) field.push(optional);
|
|
130
120
|
return field.join(".");
|
|
131
121
|
};
|
|
132
122
|
const generateNumberLikeField = () => {
|
|
133
123
|
const unsigned = Type.endsWith(" unsigned");
|
|
134
124
|
const field = typeOverride ? [typeOverride] : number;
|
|
135
|
-
if (unsigned && !typeOverride)
|
|
136
|
-
|
|
137
|
-
if (isNull && !typeOverride)
|
|
138
|
-
field.push(nullable);
|
|
125
|
+
if (unsigned && !typeOverride) field.push(nonnegative);
|
|
126
|
+
if (isNull && !typeOverride) field.push(nullable);
|
|
139
127
|
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
140
128
|
field.push(optional);
|
|
141
|
-
if (hasDefaultValue && !isGenerated)
|
|
142
|
-
|
|
143
|
-
if (isUpdateableFormat)
|
|
144
|
-
field.push(optional);
|
|
129
|
+
if (hasDefaultValue && !isGenerated) field.push(`default(${Default})`);
|
|
130
|
+
if (isUpdateableFormat) field.push(optional);
|
|
145
131
|
return field.join(".");
|
|
146
132
|
};
|
|
147
133
|
const generateEnumLikeField = () => {
|
|
148
134
|
const value = schemaType === "mysql" ? Type.replace("enum(", "").replace(")", "").replace(/,/g, ",") : EnumOptions?.map((e) => `'${e}'`).join(",");
|
|
149
135
|
const field = [`z.enum([${value}])`];
|
|
150
|
-
if (isNull)
|
|
151
|
-
field.push(nullable);
|
|
136
|
+
if (isNull) field.push(nullable);
|
|
152
137
|
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
153
138
|
field.push(optional);
|
|
154
|
-
if (hasDefaultValue && !isGenerated)
|
|
155
|
-
|
|
156
|
-
if (isUpdateableFormat)
|
|
157
|
-
field.push(optional);
|
|
139
|
+
if (hasDefaultValue && !isGenerated) field.push(`default('${Default}')`);
|
|
140
|
+
if (isUpdateableFormat) field.push(optional);
|
|
158
141
|
return field.join(".");
|
|
159
142
|
};
|
|
160
|
-
if (dateTypes[schemaType].includes(type))
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
return generateNumberLikeField();
|
|
166
|
-
if (booleanTypes[schemaType].includes(type))
|
|
167
|
-
return generateBooleanLikeField();
|
|
168
|
-
if (enumTypes[schemaType].includes(type))
|
|
169
|
-
return generateEnumLikeField();
|
|
143
|
+
if (dateTypes[schemaType].includes(type)) return generateDateLikeField();
|
|
144
|
+
if (stringTypes[schemaType].includes(type)) return generateStringLikeField();
|
|
145
|
+
if (numberTypes[schemaType].includes(type)) return generateNumberLikeField();
|
|
146
|
+
if (booleanTypes[schemaType].includes(type)) return generateBooleanLikeField();
|
|
147
|
+
if (enumTypes[schemaType].includes(type)) return generateEnumLikeField();
|
|
170
148
|
throw new Error(`Unsupported column type: ${type}`);
|
|
171
149
|
}
|
|
172
150
|
async function generate(config) {
|
|
@@ -215,8 +193,7 @@ async function generate(config) {
|
|
|
215
193
|
let useTable = true;
|
|
216
194
|
for (const text of ignoredTablesRegex) {
|
|
217
195
|
const pattern = text.substring(1, text.length - 1);
|
|
218
|
-
if (table.match(pattern) !== null)
|
|
219
|
-
useTable = false;
|
|
196
|
+
if (table.match(pattern) !== null) useTable = false;
|
|
220
197
|
}
|
|
221
198
|
return useTable;
|
|
222
199
|
});
|
|
@@ -263,8 +240,7 @@ async function generate(config) {
|
|
|
263
240
|
};
|
|
264
241
|
});
|
|
265
242
|
}
|
|
266
|
-
if (isCamelCase)
|
|
267
|
-
table = camelCase(table);
|
|
243
|
+
if (isCamelCase) table = camelCase(table);
|
|
268
244
|
let content = `import { z } from 'zod'
|
|
269
245
|
|
|
270
246
|
export const ${table} = z.object({`;
|
|
@@ -332,8 +308,7 @@ export type Selectable${camelCase(`${table}Type`, {
|
|
|
332
308
|
const file = config.suffix && config.suffix !== "" ? `${table}.${config.suffix}.ts` : `${table}.ts`;
|
|
333
309
|
const dest = path.join(dir, file);
|
|
334
310
|
dests.push(dest);
|
|
335
|
-
if (!config.silent)
|
|
336
|
-
console.log("Created:", dest);
|
|
311
|
+
if (!config.silent) console.log("Created:", dest);
|
|
337
312
|
fs.outputFileSync(dest, content);
|
|
338
313
|
}
|
|
339
314
|
if (config.origin.type === "mysql") {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mutano",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0
|
|
5
|
-
"description": "Converts Prisma/MySQL schemas to Zod interfaces",
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"description": "Converts Prisma/MySQL/PostgreSQL/SQLite schemas to Zod interfaces",
|
|
6
6
|
"author": "Alisson Cavalcante Agiani <thelinuxlich@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": "git@github.com:thelinuxlich/mutano.git",
|
|
@@ -16,14 +16,16 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@mrleebo/prisma-ast": "^0.12.1",
|
|
18
18
|
"camelcase": "^8.0.0",
|
|
19
|
-
"fs-extra": "^11.
|
|
20
|
-
"knex": "^3.0
|
|
21
|
-
"mysql2": "^3.
|
|
19
|
+
"fs-extra": "^11.3.0",
|
|
20
|
+
"knex": "^3.1.0",
|
|
21
|
+
"mysql2": "^3.14.1",
|
|
22
|
+
"pg": "^8.15.6",
|
|
23
|
+
"sqlite3": "^5.1.7"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
|
-
"@types/fs-extra": "^11.0.
|
|
25
|
-
"esbuild": "^0.
|
|
26
|
-
"typescript": "^5.
|
|
27
|
-
"vitest": "^
|
|
26
|
+
"@types/fs-extra": "^11.0.4",
|
|
27
|
+
"esbuild": "^0.25.3",
|
|
28
|
+
"typescript": "^5.8.3",
|
|
29
|
+
"vitest": "^3.1.2"
|
|
28
30
|
}
|
|
29
31
|
}
|