mutano 2.1.0 → 2.2.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/dist/main.d.ts +2 -4
- package/dist/main.js +115 -40
- package/package.json +4 -4
package/dist/main.d.ts
CHANGED
|
@@ -7,9 +7,8 @@ export interface GenerateContentParams {
|
|
|
7
7
|
isCamelCase: boolean;
|
|
8
8
|
enumDeclarations: Record<string, string[]>;
|
|
9
9
|
defaultZodHeader: string;
|
|
10
|
-
defaultKyselyHeader: string;
|
|
11
10
|
}
|
|
12
|
-
export declare function generateContent({ table, describes, config, destination, isCamelCase, enumDeclarations, defaultZodHeader,
|
|
11
|
+
export declare function generateContent({ table, describes, config, destination, isCamelCase, enumDeclarations, defaultZodHeader, }: GenerateContentParams): string;
|
|
13
12
|
export declare const defaultKyselyHeader = "import { Generated, ColumnType, Selectable, Insertable, Updateable } from 'kysely';\n\n";
|
|
14
13
|
export declare const defaultZodHeader = "import { z } from 'zod';\n\n";
|
|
15
14
|
export declare function generate(config: Config): Promise<string[] | Record<string, string>>;
|
|
@@ -46,8 +45,7 @@ export type Destination = {
|
|
|
46
45
|
type: 'kysely';
|
|
47
46
|
header?: string;
|
|
48
47
|
schemaName?: string;
|
|
49
|
-
|
|
50
|
-
suffix?: string;
|
|
48
|
+
outFile?: string;
|
|
51
49
|
};
|
|
52
50
|
export interface Config {
|
|
53
51
|
origin: {
|
package/dist/main.js
CHANGED
|
@@ -199,10 +199,10 @@ function getType(op, desc, config, destination, tableName) {
|
|
|
199
199
|
if (schemaType === "mysql") {
|
|
200
200
|
const matches = Type.match(enumRegex);
|
|
201
201
|
if (matches?.[1]) {
|
|
202
|
-
enumValues = matches[1].split(",").map((v) => v.trim());
|
|
202
|
+
enumValues = matches[1].split(",").map((v) => v.trim()).sort();
|
|
203
203
|
}
|
|
204
204
|
} else if (EnumOptions && EnumOptions.length > 0) {
|
|
205
|
-
enumValues = EnumOptions.map((e) => `'${e}'`);
|
|
205
|
+
enumValues = EnumOptions.map((e) => `'${e}'`).sort();
|
|
206
206
|
}
|
|
207
207
|
if (enumValues.length === 0) {
|
|
208
208
|
return isNull ? "string | null" : "string";
|
|
@@ -303,7 +303,16 @@ function getType(op, desc, config, destination, tableName) {
|
|
|
303
303
|
return field.join(".");
|
|
304
304
|
};
|
|
305
305
|
const generateEnumLikeField = () => {
|
|
306
|
-
|
|
306
|
+
let enumValues = [];
|
|
307
|
+
if (schemaType === "mysql") {
|
|
308
|
+
const matches = Type.match(enumRegex);
|
|
309
|
+
if (matches?.[1]) {
|
|
310
|
+
enumValues = matches[1].split(",").map((v) => v.trim()).sort();
|
|
311
|
+
}
|
|
312
|
+
} else if (EnumOptions && EnumOptions.length > 0) {
|
|
313
|
+
enumValues = [...EnumOptions].sort().map((e) => `'${e}'`);
|
|
314
|
+
}
|
|
315
|
+
const value = enumValues.join(",");
|
|
307
316
|
const field = [`z.enum([${value}])`];
|
|
308
317
|
if (isNull) field.push(nullable);
|
|
309
318
|
else if (hasDefaultValue || !hasDefaultValue && isGenerated)
|
|
@@ -327,35 +336,16 @@ function generateContent({
|
|
|
327
336
|
destination,
|
|
328
337
|
isCamelCase,
|
|
329
338
|
enumDeclarations: enumDeclarations2,
|
|
330
|
-
defaultZodHeader: defaultZodHeader2
|
|
331
|
-
defaultKyselyHeader: defaultKyselyHeader2
|
|
339
|
+
defaultZodHeader: defaultZodHeader2
|
|
332
340
|
}) {
|
|
333
341
|
let content = "";
|
|
342
|
+
const schemaType = config.origin.type;
|
|
334
343
|
if (destination.type === "kysely") {
|
|
335
|
-
const header = destination.header;
|
|
336
|
-
const schemaName = destination.schemaName || "DB";
|
|
337
|
-
content = header ? `${header}
|
|
338
|
-
|
|
339
|
-
` : defaultKyselyHeader2;
|
|
340
|
-
content += `// JSON type definitions
|
|
341
|
-
export type Json = ColumnType<JsonValue, string, string>;
|
|
342
|
-
|
|
343
|
-
export type JsonArray = JsonValue[];
|
|
344
|
-
|
|
345
|
-
export type JsonObject = {
|
|
346
|
-
[x: string]: JsonValue | undefined;
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
export type JsonPrimitive = boolean | number | string | null;
|
|
350
|
-
|
|
351
|
-
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
|
|
352
|
-
|
|
353
|
-
`;
|
|
354
344
|
content += `// Kysely type definitions for ${table}
|
|
355
345
|
`;
|
|
356
346
|
content += `
|
|
357
347
|
// This interface defines the structure of the '${table}' table
|
|
358
|
-
export interface ${camelCase(table, { pascalCase: true })}
|
|
348
|
+
export interface ${camelCase(table, { pascalCase: true })} {`;
|
|
359
349
|
for (const desc of describes) {
|
|
360
350
|
const field = isCamelCase ? camelCase(desc.Field) : desc.Field;
|
|
361
351
|
const type = getType("table", desc, config, destination, table);
|
|
@@ -365,9 +355,13 @@ export interface ${camelCase(table, { pascalCase: true })}Table {`;
|
|
|
365
355
|
const isDefaultGenerated = desc.Extra.toLowerCase().includes("default_generated");
|
|
366
356
|
const isNullable = desc.Null === "YES";
|
|
367
357
|
const isJsonField = desc.Type.toLowerCase().includes("json");
|
|
358
|
+
const hasDefaultValue = desc.Default !== null;
|
|
359
|
+
const isEnum = schemaType !== "sqlite" && enumTypes[schemaType].includes(
|
|
360
|
+
schemaType === "mysql" ? desc.Type.split("(")[0].split(" ")[0] : desc.Type
|
|
361
|
+
);
|
|
368
362
|
if (isJsonField) {
|
|
369
363
|
kyselyType = "Json";
|
|
370
|
-
} else if (isAutoIncrement || isDefaultGenerated) {
|
|
364
|
+
} else if (isAutoIncrement || isDefaultGenerated || isEnum && hasDefaultValue) {
|
|
371
365
|
kyselyType = `Generated<${kyselyType}>`;
|
|
372
366
|
}
|
|
373
367
|
if (isNullable && !isJsonField) {
|
|
@@ -382,15 +376,10 @@ export interface ${camelCase(table, { pascalCase: true })}Table {`;
|
|
|
382
376
|
content = `${content}
|
|
383
377
|
}
|
|
384
378
|
|
|
385
|
-
//
|
|
386
|
-
export
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Use these types for inserting, selecting and updating the table
|
|
391
|
-
export type ${camelCase(table, { pascalCase: true })} = Selectable<${camelCase(table, { pascalCase: true })}Table>;
|
|
392
|
-
export type New${camelCase(table, { pascalCase: true })} = Insertable<${camelCase(table, { pascalCase: true })}Table>;
|
|
393
|
-
export type ${camelCase(table, { pascalCase: true })}Update = Updateable<${camelCase(table, { pascalCase: true })}Table>;
|
|
379
|
+
// Helper types for ${table}
|
|
380
|
+
export type Selectable${camelCase(table, { pascalCase: true })} = Selectable<${camelCase(table, { pascalCase: true })}>;
|
|
381
|
+
export type Insertable${camelCase(table, { pascalCase: true })} = Insertable<${camelCase(table, { pascalCase: true })}>;
|
|
382
|
+
export type Updateable${camelCase(table, { pascalCase: true })} = Updateable<${camelCase(table, { pascalCase: true })}>;
|
|
394
383
|
`;
|
|
395
384
|
} else if (destination.type === "ts") {
|
|
396
385
|
const modelType = destination.modelType || "interface";
|
|
@@ -553,6 +542,7 @@ async function generate(config) {
|
|
|
553
542
|
let prismaTables = [];
|
|
554
543
|
let schema = null;
|
|
555
544
|
let db = null;
|
|
545
|
+
const kyselyTableContents = {};
|
|
556
546
|
if (config.destinations.length === 0) {
|
|
557
547
|
throw new Error("Empty destinations object.");
|
|
558
548
|
}
|
|
@@ -696,7 +686,7 @@ async function generate(config) {
|
|
|
696
686
|
AND column_name = $3
|
|
697
687
|
)
|
|
698
688
|
ORDER BY
|
|
699
|
-
e.
|
|
689
|
+
e.enumlabel
|
|
700
690
|
`,
|
|
701
691
|
[schema2, table, column.Field]
|
|
702
692
|
);
|
|
@@ -769,16 +759,21 @@ async function generate(config) {
|
|
|
769
759
|
if (!config.destinations || config.destinations.length === 0) {
|
|
770
760
|
throw new Error("No destinations specified");
|
|
771
761
|
}
|
|
772
|
-
|
|
762
|
+
const kyselyDestinations = config.destinations.filter(
|
|
763
|
+
(d) => d.type === "kysely"
|
|
764
|
+
);
|
|
765
|
+
const nonKyselyDestinations = config.destinations.filter(
|
|
766
|
+
(d) => d.type !== "kysely"
|
|
767
|
+
);
|
|
768
|
+
for (const destination of nonKyselyDestinations) {
|
|
773
769
|
const content = generateContent({
|
|
774
770
|
table,
|
|
775
|
-
describes,
|
|
771
|
+
describes: describes.sort((a, b) => a.Field.localeCompare(b.Field)),
|
|
776
772
|
config,
|
|
777
773
|
destination,
|
|
778
774
|
isCamelCase: isCamelCase === true,
|
|
779
775
|
enumDeclarations,
|
|
780
|
-
defaultZodHeader
|
|
781
|
-
defaultKyselyHeader
|
|
776
|
+
defaultZodHeader
|
|
782
777
|
});
|
|
783
778
|
const suffix = destination.suffix || "";
|
|
784
779
|
const folder = destination.folder || ".";
|
|
@@ -792,8 +787,88 @@ async function generate(config) {
|
|
|
792
787
|
fs.outputFileSync(dest, content);
|
|
793
788
|
}
|
|
794
789
|
}
|
|
790
|
+
for (const destination of kyselyDestinations) {
|
|
791
|
+
const content = generateContent({
|
|
792
|
+
table,
|
|
793
|
+
describes: describes.sort((a, b) => a.Field.localeCompare(b.Field)),
|
|
794
|
+
config,
|
|
795
|
+
destination,
|
|
796
|
+
isCamelCase: isCamelCase === true,
|
|
797
|
+
enumDeclarations,
|
|
798
|
+
defaultZodHeader
|
|
799
|
+
});
|
|
800
|
+
const outFile = destination.outFile || "db.ts";
|
|
801
|
+
if (!kyselyTableContents[outFile]) {
|
|
802
|
+
kyselyTableContents[outFile] = [];
|
|
803
|
+
}
|
|
804
|
+
kyselyTableContents[outFile].push({
|
|
805
|
+
table,
|
|
806
|
+
content
|
|
807
|
+
});
|
|
808
|
+
if (config.dryRun) {
|
|
809
|
+
const tempKey = `${table}.kysely.temp`;
|
|
810
|
+
dryRunOutput[tempKey] = content;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
795
813
|
}
|
|
796
814
|
if (db) await db.destroy();
|
|
815
|
+
for (const [outFile, tableContents] of Object.entries(kyselyTableContents)) {
|
|
816
|
+
if (tableContents.length === 0) continue;
|
|
817
|
+
const kyselyDestination = config.destinations.find(
|
|
818
|
+
(d) => d.type === "kysely"
|
|
819
|
+
);
|
|
820
|
+
const header = kyselyDestination?.header || defaultKyselyHeader;
|
|
821
|
+
const schemaName = kyselyDestination?.schemaName || "DB";
|
|
822
|
+
let consolidatedContent = `${header}
|
|
823
|
+
|
|
824
|
+
// JSON type definitions
|
|
825
|
+
export type Json = ColumnType<JsonValue, string, string>;
|
|
826
|
+
|
|
827
|
+
export type JsonArray = JsonValue[];
|
|
828
|
+
|
|
829
|
+
export type JsonObject = {
|
|
830
|
+
[x: string]: JsonValue | undefined;
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
export type JsonPrimitive = boolean | number | string | null;
|
|
834
|
+
|
|
835
|
+
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
|
|
836
|
+
|
|
837
|
+
`;
|
|
838
|
+
consolidatedContent += "// Table Interfaces\n";
|
|
839
|
+
for (const { content } of tableContents) {
|
|
840
|
+
consolidatedContent += `${content}
|
|
841
|
+
`;
|
|
842
|
+
}
|
|
843
|
+
consolidatedContent += `
|
|
844
|
+
// Database Interface
|
|
845
|
+
export interface ${schemaName} {
|
|
846
|
+
`;
|
|
847
|
+
const sortedTableEntries = tableContents.map(({ table }) => {
|
|
848
|
+
const pascalTable = camelCase(table, { pascalCase: true });
|
|
849
|
+
const tableKey = isCamelCase ? camelCase(table) : table;
|
|
850
|
+
return { tableKey, pascalTable };
|
|
851
|
+
}).sort((a, b) => a.tableKey.localeCompare(b.tableKey));
|
|
852
|
+
for (const { tableKey, pascalTable } of sortedTableEntries) {
|
|
853
|
+
consolidatedContent += ` ${tableKey}: ${pascalTable};
|
|
854
|
+
`;
|
|
855
|
+
}
|
|
856
|
+
consolidatedContent += "}\n";
|
|
857
|
+
if (config.dryRun) {
|
|
858
|
+
const fileName = path.basename(outFile);
|
|
859
|
+
dryRunOutput[fileName] = consolidatedContent;
|
|
860
|
+
for (const key of Object.keys(dryRunOutput)) {
|
|
861
|
+
if (key.endsWith(".kysely.temp")) {
|
|
862
|
+
delete dryRunOutput[key];
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
} else {
|
|
866
|
+
const dest = path.resolve(outFile);
|
|
867
|
+
dests.push(dest);
|
|
868
|
+
if (!config.silent) console.log("Created:", dest);
|
|
869
|
+
fs.outputFileSync(dest, consolidatedContent);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
797
872
|
return config.dryRun ? dryRunOutput : dests;
|
|
798
873
|
}
|
|
799
874
|
export {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mutano",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.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",
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
"fs-extra": "^11.3.0",
|
|
20
20
|
"knex": "^3.1.0",
|
|
21
21
|
"mysql2": "^3.14.1",
|
|
22
|
-
"pg": "^8.
|
|
22
|
+
"pg": "^8.16.0",
|
|
23
23
|
"sqlite3": "^5.1.7"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/fs-extra": "^11.0.4",
|
|
27
|
-
"esbuild": "^0.25.
|
|
27
|
+
"esbuild": "^0.25.4",
|
|
28
28
|
"ts-node": "^10.9.2",
|
|
29
29
|
"tsx": "^4.19.4",
|
|
30
30
|
"typescript": "^5.8.3",
|
|
31
|
-
"vitest": "^3.1.
|
|
31
|
+
"vitest": "^3.1.3"
|
|
32
32
|
}
|
|
33
33
|
}
|