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 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 instead of writing to files
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: Json;
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
- "folder": "kysely",
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
- You can use complex TypeScript types in the `@ts` comment:
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 extractTSExpression = (comment) => {
11
- const start = comment.indexOf("@ts(");
10
+ const extractTypeExpression = (comment, prefix) => {
11
+ const start = comment.indexOf(prefix);
12
12
  if (start === -1) return null;
13
- const typeLen = 4;
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
- function extractZodExpression(comment) {
32
- const zodStart = comment.indexOf("@zod(");
33
- if (zodStart === -1) return null;
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", "Decimal", "BigInt", "Bytes", "Json"]
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
- if (isJsonField) {
363
- kyselyType = "Json";
364
- } else if (isAutoIncrement || isDefaultGenerated || isEnum && hasDefaultValue) {
365
- kyselyType = `Generated<${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.3.0",
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",