mutano 1.1.0 → 2.0.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.
Files changed (4) hide show
  1. package/README.md +534 -27
  2. package/dist/main.d.ts +70 -14
  3. package/dist/main.js +575 -96
  4. package/package.json +29 -29
package/README.md CHANGED
@@ -1,13 +1,21 @@
1
- # mutano
1
+ # Mutano
2
2
 
3
- Converts Prisma/MySQL/PostgreSQL/SQLite schemas to Zod interfaces
3
+ Converts Prisma/MySQL/PostgreSQL/SQLite schemas to Zod schemas, TypeScript interfaces, or Kysely type definitions
4
+
5
+ ## Features
6
+
7
+ - Generates Zod schemas, Typescript interfaces or Kysely type definitions for MySQL, PostgreSQL, SQLite, and Prisma schemas
8
+ - Supports camelCase conversion
9
+ - Handles nullable, default, auto-increment and enum fields
10
+ - Supports custom type overrides via configuration or database comments
11
+ - Intelligently handles field nullability based on operation type (table, insertable, updateable, selectable)
4
12
 
5
13
  ## Installation
6
14
 
7
15
  Install `mutano` with npm
8
16
 
9
17
  ```bash
10
- npm install mutano --save-dev
18
+ npm install mutano
11
19
  ```
12
20
 
13
21
  ## Usage/Examples
@@ -17,17 +25,96 @@ Create user table:
17
25
  ```sql
18
26
  CREATE TABLE `user` (
19
27
  `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
28
+ `name` varchar(255) NOT NULL COMMENT '@zod(z.string().min(10).max(255))', -- this will override the Zod type
21
29
  `username` varchar(255) NOT NULL,
22
30
  `password` varchar(255) NOT NULL,
23
31
  `profile_picture` varchar(255) DEFAULT NULL,
32
+ `metadata` json NOT NULL COMMENT '@ts(Record<string, unknown>)', -- this will override the TypeScript type
24
33
  `role` enum('admin','user') NOT NULL,
25
34
  PRIMARY KEY (`id`)
26
35
  );
27
36
  ```
28
37
  Use the mutano API:
29
38
 
30
- ### MySQL Example
39
+ ### MySQL Example with Zod Schemas
40
+
41
+ ```typescript
42
+ import { generate } from 'mutano'
43
+
44
+ await generate({
45
+ origin: {
46
+ type: 'mysql',
47
+ host: '127.0.0.1',
48
+ port: 3306,
49
+ user: 'root',
50
+ password: 'secret',
51
+ database: 'myapp',
52
+ overrideTypes: {
53
+ json: 'z.record(z.string())'
54
+ }
55
+ },
56
+ destinations: [{
57
+ type: 'zod',
58
+ useDateType: true,
59
+ useTrim: false,
60
+ nullish: false,
61
+ folder: './generated',
62
+ suffix: 'schema'
63
+ }]
64
+ })
65
+ ```
66
+
67
+ ### MySQL Example with TypeScript Type Aliases (Instead of Interfaces)
68
+
69
+ ```typescript
70
+ import { generate } from 'mutano'
71
+
72
+ await generate({
73
+ origin: {
74
+ type: 'mysql',
75
+ host: '127.0.0.1',
76
+ port: 3306,
77
+ user: 'root',
78
+ password: 'secret',
79
+ database: 'myapp',
80
+ overrideTypes: {
81
+ json: 'z.record(z.string())'
82
+ }
83
+ },
84
+ destinations: [{
85
+ type: 'ts',
86
+ modelType: 'type', // Generate TypeScript type aliases instead of interfaces
87
+ folder: './types',
88
+ suffix: 'types'
89
+ }]
90
+ })
91
+ ```
92
+
93
+ ### MySQL Example with Custom Header for TypeScript
94
+
95
+ ```typescript
96
+ import { generate } from 'mutano'
97
+
98
+ await generate({
99
+ origin: {
100
+ type: 'mysql',
101
+ host: '127.0.0.1',
102
+ port: 3306,
103
+ user: 'root',
104
+ password: 'secret',
105
+ database: 'myapp',
106
+ overrideTypes: {
107
+ json: 'z.record(z.string())'
108
+ }
109
+ },
110
+ destinations: [{
111
+ type: 'ts',
112
+ header: "import type { CustomType } from './types';\nimport type { BaseModel } from './models';"
113
+ }]
114
+ })
115
+ ```
116
+
117
+ ### MySQL Example with Custom Header for Zod
31
118
 
32
119
  ```typescript
33
120
  import { generate } from 'mutano'
@@ -44,7 +131,66 @@ await generate({
44
131
  json: 'z.record(z.string())'
45
132
  }
46
133
  },
134
+ destinations: [{
135
+ type: 'zod',
136
+ header: "import { z } from 'zod';\nimport { CustomValidator } from './validators';"
137
+ }]
138
+ })
139
+ ```
140
+
141
+ ### MySQL Example with Kysely Type Definitions (Custom Schema Name)
142
+
143
+ ```typescript
144
+ import { generate } from 'mutano'
145
+
146
+ await generate({
147
+ origin: {
148
+ type: 'mysql',
149
+ host: '127.0.0.1',
150
+ port: 3306,
151
+ user: 'root',
152
+ password: 'secret',
153
+ database: 'myapp',
154
+ overrideTypes: {
155
+ json: 'z.record(z.string())'
156
+ }
157
+ },
158
+ destinations: [{
159
+ type: 'kysely',
160
+ schemaName: 'Database', // Default is 'DB'
161
+ header: "import { Generated, ColumnType } from 'kysely';\nimport { CustomTypes } from './types';",
162
+ folder: './db/types',
163
+ suffix: 'db'
164
+ }]
165
+ })
166
+ ```
167
+
168
+ ### Example with Dry Run Option
169
+
170
+ ```typescript
171
+ import { generate } from 'mutano'
172
+
173
+ // Generate without writing to disk
174
+ const output = await generate({
175
+ origin: {
176
+ type: 'mysql',
177
+ host: '127.0.0.1',
178
+ port: 3306,
179
+ user: 'root',
180
+ password: 'secret',
181
+ database: 'myapp'
182
+ },
183
+ destinations: [{
184
+ type: 'zod'
185
+ }],
186
+ dryRun: true // Return content instead of writing to files
47
187
  })
188
+
189
+ // Output is an object where keys are filenames and values are file content
190
+ console.log(Object.keys(output)) // ['user.ts', 'product.ts', ...]
191
+
192
+ // You can access the content for a specific file
193
+ console.log(output['user.ts'])
48
194
  ```
49
195
 
50
196
  ### PostgreSQL Example
@@ -65,6 +211,10 @@ await generate({
65
211
  jsonb: 'z.record(z.string())'
66
212
  }
67
213
  },
214
+ destinations: [{
215
+ type: 'zod',
216
+ useDateType: true
217
+ }]
68
218
  })
69
219
  ```
70
220
 
@@ -81,13 +231,59 @@ await generate({
81
231
  json: 'z.record(z.string())'
82
232
  }
83
233
  },
234
+ destinations: [{
235
+ type: 'ts'
236
+ }]
237
+ })
238
+ ```
239
+
240
+ ### Example with Multiple Destinations
241
+
242
+ ```typescript
243
+ import { generate } from 'mutano'
244
+
245
+ await generate({
246
+ origin: {
247
+ type: 'mysql',
248
+ host: '127.0.0.1',
249
+ port: 3306,
250
+ user: 'root',
251
+ password: 'secret',
252
+ database: 'myapp',
253
+ overrideTypes: {
254
+ json: 'z.record(z.string())'
255
+ }
256
+ },
257
+ destinations: [
258
+ {
259
+ type: 'zod',
260
+ useDateType: true,
261
+ folder: './generated/zod',
262
+ suffix: 'schema'
263
+ },
264
+ {
265
+ type: 'ts',
266
+ folder: './generated/types',
267
+ suffix: 'type'
268
+ },
269
+ {
270
+ type: 'kysely',
271
+ folder: './generated/kysely',
272
+ suffix: 'db'
273
+ }
274
+ ]
84
275
  })
85
276
  ```
86
277
 
87
- The generator will create a `user.ts` file with the following contents:
278
+ This will generate all three types of output files for each table in your database, placing them in separate folders with appropriate suffixes.
279
+
280
+ The generator will create `user.type.ts`, `user.schema.ts`, and `user.db.ts` files with the following contents:
281
+
282
+ ### Zod Schema Output Example with Custom Header
88
283
 
89
284
  ```typescript
90
- import z from 'zod'
285
+ import { z } from 'zod';
286
+ import { CustomValidator } from './validators';
91
287
 
92
288
  export const user = z.object({
93
289
  id: z.number().nonnegative(),
@@ -129,6 +325,224 @@ export type UpdateableUserType = z.infer<typeof updateable_user>
129
325
  export type SelectableUserType = z.infer<typeof selectable_user>
130
326
  ```
131
327
 
328
+ ### TypeScript Interface Output Example with Custom Header
329
+
330
+ ```typescript
331
+ import { CustomType } from './types';
332
+ import { BaseModel } from './models';
333
+
334
+ // TypeScript interfaces for user
335
+
336
+ export interface User {
337
+ id: number;
338
+ name: string;
339
+ username: string;
340
+ password: string;
341
+ profile_picture: string | null;
342
+ metadata: Record<string, unknown>; // Custom type from @ts comment
343
+ role: 'admin' | 'user';
344
+ }
345
+
346
+ export interface InsertableUser {
347
+ name: string | null; // Optional because it has a default value
348
+ username: string;
349
+ password: string;
350
+ profile_picture: string | null;
351
+ metadata: Record<string, unknown>; // Custom type from @ts comment
352
+ role: 'admin' | 'user';
353
+ }
354
+
355
+ export interface UpdateableUser {
356
+ name: string | null; // Optional for updates
357
+ username: string | null; // Optional for updates
358
+ password: string | null; // Optional for updates
359
+ profile_picture: string | null;
360
+ metadata: Record<string, unknown> | null; // Custom type from @ts comment, optional for updates
361
+ role: 'admin' | 'user' | null; // Optional for updates
362
+ }
363
+
364
+ export interface SelectableUser {
365
+ id: number;
366
+ name: string;
367
+ username: string;
368
+ password: string;
369
+ profile_picture: string | null;
370
+ metadata: Record<string, unknown>; // Custom type from @ts comment
371
+ role: 'admin' | 'user';
372
+ }
373
+ ```
374
+
375
+ ### TypeScript Interface Output Example (Enum Type for Enums)
376
+
377
+ ```typescript
378
+ // TypeScript interfaces for user
379
+
380
+ // Enum declarations
381
+ enum RoleEnum {
382
+ admin = 'admin',
383
+ user = 'user'
384
+ }
385
+
386
+ export interface User {
387
+ id: number;
388
+ name: string;
389
+ username: string;
390
+ password: string;
391
+ profile_picture: string | null;
392
+ role: RoleEnum;
393
+ }
394
+
395
+ export interface InsertableUser {
396
+ name: string | null; // Optional because it has a default value
397
+ username: string;
398
+ password: string;
399
+ profile_picture: string | null;
400
+ role: RoleEnum;
401
+ }
402
+
403
+ export interface UpdateableUser {
404
+ name: string | null; // Optional for updates
405
+ username: string | null; // Optional for updates
406
+ password: string | null; // Optional for updates
407
+ profile_picture: string | null;
408
+ role: RoleEnum | null; // Optional for updates
409
+ }
410
+
411
+ export interface SelectableUser {
412
+ id: number;
413
+ name: string;
414
+ username: string;
415
+ password: string;
416
+ profile_picture: string | null;
417
+ role: RoleEnum;
418
+ }
419
+ ```
420
+
421
+ ### TypeScript Type Alias Output Example
422
+
423
+ ```typescript
424
+ // TypeScript types for user
425
+
426
+ export type User = {
427
+ id: number;
428
+ name: string;
429
+ username: string;
430
+ password: string;
431
+ profile_picture: string | null;
432
+ role: 'admin' | 'user';
433
+ }
434
+
435
+ export type InsertableUser = {
436
+ name: string | null; // Optional because it has a default value
437
+ username: string;
438
+ password: string;
439
+ profile_picture: string | null;
440
+ role: 'admin' | 'user';
441
+ }
442
+
443
+ export type UpdateableUser = {
444
+ name: string | null; // Optional for updates
445
+ username: string | null; // Optional for updates
446
+ password: string | null; // Optional for updates
447
+ profile_picture: string | null;
448
+ role: 'admin' | 'user' | null; // Optional for updates
449
+ }
450
+
451
+ export type SelectableUser = {
452
+ id: number;
453
+ name: string;
454
+ username: string;
455
+ password: string;
456
+ profile_picture: string | null;
457
+ role: 'admin' | 'user';
458
+ }
459
+ ```
460
+
461
+ ### Kysely Type Definitions Output Example
462
+
463
+ ```typescript
464
+ import { Generated, ColumnType, Selectable, Insertable, Updateable } from 'kysely';
465
+
466
+ // JSON type definitions
467
+ export type Json = ColumnType<JsonValue, string, string>;
468
+
469
+ export type JsonArray = JsonValue[];
470
+
471
+ export type JsonObject = {
472
+ [x: string]: JsonValue | undefined;
473
+ };
474
+
475
+ export type JsonPrimitive = boolean | number | string | null;
476
+
477
+ export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
478
+
479
+ // Kysely type definitions for user
480
+
481
+ // This interface defines the structure of the 'user' table
482
+ export interface UserTable {
483
+ id: Generated<number>;
484
+ name: string;
485
+ username: string;
486
+ password: string;
487
+ profile_picture: string | null;
488
+ metadata: Json;
489
+ role: 'admin' | 'user';
490
+ }
491
+
492
+ // Define the database interface
493
+ export interface DB {
494
+ user: UserTable;
495
+ }
496
+
497
+ // Use these types for inserting, selecting and updating the table
498
+ export type User = Selectable<UserTable>;
499
+ export type NewUser = Insertable<UserTable>;
500
+ export type UserUpdate = Updateable<UserTable>;
501
+ ```
502
+
503
+ ### Kysely Type Definitions Output Example with Custom Schema Name
504
+
505
+ ```typescript
506
+ import { Generated, ColumnType, Selectable, Insertable, Updateable } from 'kysely';
507
+ import { CustomTypes } from './types';
508
+
509
+ // JSON type definitions
510
+ export type Json = ColumnType<JsonValue, string, string>;
511
+
512
+ export type JsonArray = JsonValue[];
513
+
514
+ export type JsonObject = {
515
+ [x: string]: JsonValue | undefined;
516
+ };
517
+
518
+ export type JsonPrimitive = boolean | number | string | null;
519
+
520
+ export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
521
+
522
+ // Kysely type definitions for user
523
+
524
+ // This interface defines the structure of the 'user' table
525
+ export interface UserTable {
526
+ id: Generated<number>;
527
+ name: string;
528
+ username: string;
529
+ password: string;
530
+ profile_picture: string | null;
531
+ metadata: Json;
532
+ role: 'admin' | 'user';
533
+ }
534
+
535
+ // Define the database interface
536
+ export interface Database {
537
+ user: UserTable;
538
+ }
539
+
540
+ // Use these types for inserting, selecting and updating the table
541
+ export type User = Selectable<UserTable>;
542
+ export type NewUser = Insertable<UserTable>;
543
+ export type UserUpdate = Updateable<UserTable>;
544
+ ```
545
+
132
546
  ## Config
133
547
 
134
548
  ```json
@@ -177,40 +591,66 @@ export type SelectableUserType = z.infer<typeof selectable_user>
177
591
  "Json": "z.record(z.string())"
178
592
  }
179
593
  },
594
+ "destinations": [
595
+ {
596
+ "type": "zod",
597
+ "useDateType": true,
598
+ "useTrim": false,
599
+ "nullish": false,
600
+ "requiredString": false,
601
+ "header": "import { z } from 'zod';\nimport { CustomValidator } from './validators';",
602
+ "folder": "@zod",
603
+ "suffix": "table"
604
+ },
605
+ {
606
+ "type": "ts",
607
+ "enumType": "union",
608
+ "modelType": "interface",
609
+ "header": "import { CustomType } from './types';\nimport { BaseModel } from './models';",
610
+ "folder": "types",
611
+ "suffix": "type"
612
+ },
613
+ {
614
+ "type": "kysely",
615
+ "schemaName": "Database",
616
+ "header": "import { Generated, ColumnType } from 'kysely';\nimport { CustomTypes } from './types';",
617
+ "folder": "kysely",
618
+ "suffix": "db"
619
+ }
620
+ ],
180
621
  "tables": ["user", "log"],
181
622
  "ignore": ["log", "/^temp/"],
182
- "folder": "@zod",
183
- "suffix": "table",
184
623
  "camelCase": false,
185
- "nullish": false,
186
- "requiredString": false,
187
- "useTrim": false,
188
- "useDateType": false,
189
624
  "silent": false,
190
- "zodCommentTypes": true,
191
- "overrideTypes": {
192
- "tinyint": "z.boolean()"
193
- }
625
+ "dryRun": false,
626
+ "magicComments": true
194
627
  }
195
628
  ```
196
629
 
197
630
  | Option | Description |
198
631
  | ------ | ----------- |
199
- | tables | Filter the tables to include only those specified. |
200
- | ignore | Filter the tables to exclude those specified. If a table name begins and ends with "/", it will be processed as a regular expression. |
201
- | folder | Specify the output directory. |
202
- | suffix | Suffix to the name of a generated file. (eg: `user.table.ts`) |
632
+ | destinations | An array of destination configurations to generate multiple output formats from a single origin |
633
+ | destinations[].type | The type of output to generate: "zod", "ts", or "kysely" |
634
+ | destinations[].useDateType | (Zod only) Use a specialized Zod type for date-like fields instead of string |
635
+ | destinations[].useTrim | (Zod only) Use `z.string().trim()` instead of `z.string()` |
636
+ | destinations[].nullish | (Zod only) Set schema as `nullish` instead of `nullable` |
637
+ | destinations[].requiredString | (Zod only) Add `min(1)` for string schema |
638
+ | destinations[].enumType | (TypeScript only) How to represent enum types: "union" (default) or "enum" |
639
+ | destinations[].modelType | (TypeScript only) How to represent models: "interface" (default) or "type" |
640
+ | destinations[].schemaName | (Kysely only) Name of the database interface (default: "DB") |
641
+ | destinations[].header | Custom header to include at the beginning of generated files (e.g., custom imports) |
642
+ | destinations[].folder | Specify the output directory for the generated files |
643
+ | destinations[].suffix | Suffix to the name of a generated file (eg: `user.table.ts`) |
644
+ | tables | Filter the tables to include only those specified |
645
+ | ignore | Filter the tables to exclude those specified. If a table name begins and ends with "/", it will be processed as a regular expression |
203
646
  | camelCase | Convert all table names and their properties to camelcase. (eg: `profile_picture` becomes `profilePicture`) |
204
- | nullish | Set schema as `nullish` instead of `nullable` |
205
- | requiredString | Add `min(1)` for string schema |
206
- | useDateType | Use a specialized Zod type for date-like fields instead of string
207
- | useTrim | Use `z.string().trim()` instead of `z.string()` |
208
647
  | silent | Don't log anything to the console |
209
- | magicComments | Use @zod comment to override entire type (unsupported by SQLite) |
648
+ | dryRun | When true, doesn't write files to disk but returns an object with filenames as keys and generated content as values |
649
+ | magicComments | Use @zod and @ts comments to override types (unsupported by SQLite) |
210
650
 
211
651
  ## overrideTypes
212
652
 
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:
653
+ You can override the default 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
654
 
215
655
  ### MySQL overrideTypes
216
656
 
@@ -279,4 +719,71 @@ You can override the default Zod type for a specific column type. This is specif
279
719
  }
280
720
  }
281
721
  }
722
+ ```
723
+
724
+ ## Magic Comments
725
+
726
+ ### @zod Comments
727
+
728
+ You can use the `@zod` comment to override the Zod type for a specific column. This is useful when you want to add custom validation or transformation to a field.
729
+
730
+ ```sql
731
+ CREATE TABLE `user` (
732
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
733
+ `name` varchar(255) NOT NULL COMMENT '@zod(z.string().min(10).max(255))',
734
+ `email` varchar(255) NOT NULL COMMENT '@zod(z.string().email())',
735
+ PRIMARY KEY (`id`)
736
+ );
737
+ ```
738
+
739
+ This will generate:
740
+
741
+ ```typescript
742
+ export const user = z.object({
743
+ id: z.number().nonnegative(),
744
+ name: z.string().min(10).max(255),
745
+ email: z.string().email(),
746
+ })
747
+ ```
748
+
749
+ ### @ts Comments
750
+
751
+ You can use the `@ts` comment to override the TypeScript type for a specific column. This is useful when you want to specify a more precise type for a field.
752
+
753
+ ```sql
754
+ CREATE TABLE `user` (
755
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
756
+ `metadata` json NOT NULL COMMENT '@ts(Record<string, unknown>)',
757
+ `settings` json NOT NULL COMMENT '@ts(UserSettings)',
758
+ PRIMARY KEY (`id`)
759
+ );
760
+ ```
761
+
762
+ This will generate:
763
+
764
+ ```typescript
765
+ export interface User {
766
+ id: number;
767
+ metadata: Record<string, unknown>;
768
+ settings: UserSettings;
769
+ }
770
+ ```
771
+
772
+ You can use complex TypeScript types in the `@ts` comment:
773
+
774
+ ```sql
775
+ CREATE TABLE `product` (
776
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
777
+ `variants` json NOT NULL COMMENT '@ts(Array<{ id: string; price: number; stock: number }>)',
778
+ PRIMARY KEY (`id`)
779
+ );
780
+ ```
781
+
782
+ This will generate:
783
+
784
+ ```typescript
785
+ export interface Product {
786
+ id: number;
787
+ variants: Array<{ id: string; price: number; stock: number }>;
788
+ }
282
789
  ```