sizuku 0.3.1 → 0.4.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 +104 -1
- package/dist/config/index.d.mts +58 -0
- package/dist/config/index.mjs +95 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +1615 -0
- package/package.json +31 -17
- package/dist/cli/main.d.ts +0 -10
- package/dist/cli/main.js +0 -61
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -51
- package/dist/config/index.d.ts +0 -27
- package/dist/config/index.js +0 -85
- package/dist/config/loader.d.ts +0 -19
- package/dist/config/loader.js +0 -30
- package/dist/generator/engine.d.ts +0 -3
- package/dist/generator/engine.js +0 -63
- package/dist/generator/mermaid-er/config/index.d.ts +0 -10
- package/dist/generator/mermaid-er/config/index.js +0 -12
- package/dist/generator/mermaid-er/core/extract-relations.d.ts +0 -8
- package/dist/generator/mermaid-er/core/extract-relations.js +0 -17
- package/dist/generator/mermaid-er/core.d.ts +0 -6
- package/dist/generator/mermaid-er/core.js +0 -54
- package/dist/generator/mermaid-er/generator/er-content.d.ts +0 -20
- package/dist/generator/mermaid-er/generator/er-content.js +0 -23
- package/dist/generator/mermaid-er/generator/index.d.ts +0 -2
- package/dist/generator/mermaid-er/generator/index.js +0 -2
- package/dist/generator/mermaid-er/generator/relation-line.d.ts +0 -12
- package/dist/generator/mermaid-er/generator/relation-line.js +0 -13
- package/dist/generator/mermaid-er/generator.d.ts +0 -3
- package/dist/generator/mermaid-er/generator.js +0 -14
- package/dist/generator/mermaid-er/index.d.ts +0 -12
- package/dist/generator/mermaid-er/index.js +0 -33
- package/dist/generator/mermaid-er/relationship/build-relation-line.d.ts +0 -14
- package/dist/generator/mermaid-er/relationship/build-relation-line.js +0 -34
- package/dist/generator/mermaid-er/types.d.ts +0 -21
- package/dist/generator/mermaid-er/types.js +0 -1
- package/dist/generator/mermaid-er/validator/index.d.ts +0 -8
- package/dist/generator/mermaid-er/validator/index.js +0 -74
- package/dist/generator/mermaid-er/validator/is-relationship.d.ts +0 -7
- package/dist/generator/mermaid-er/validator/is-relationship.js +0 -8
- package/dist/generator/mermaid-er/validator/parse-relation-line.d.ts +0 -12
- package/dist/generator/mermaid-er/validator/parse-relation-line.js +0 -20
- package/dist/generator/mermaid-er/validator/parse-table-info.d.ts +0 -2
- package/dist/generator/mermaid-er/validator/parse-table-info.js +0 -71
- package/dist/generator/mermaid-er/validator/remove-duplicate-relations.d.ts +0 -7
- package/dist/generator/mermaid-er/validator/remove-duplicate-relations.js +0 -9
- package/dist/generator/valibot/config/index.d.ts +0 -7
- package/dist/generator/valibot/config/index.js +0 -13
- package/dist/generator/valibot/core/extract-schema.d.ts +0 -5
- package/dist/generator/valibot/core/extract-schema.js +0 -173
- package/dist/generator/valibot/core.d.ts +0 -5
- package/dist/generator/valibot/core.js +0 -39
- package/dist/generator/valibot/generator/infer-input.d.ts +0 -5
- package/dist/generator/valibot/generator/infer-input.js +0 -8
- package/dist/generator/valibot/generator/relation-valibot-code.d.ts +0 -13
- package/dist/generator/valibot/generator/relation-valibot-code.js +0 -19
- package/dist/generator/valibot/generator/valibot-code.d.ts +0 -15
- package/dist/generator/valibot/generator/valibot-code.js +0 -16
- package/dist/generator/valibot/generator/valibot.d.ts +0 -14
- package/dist/generator/valibot/generator/valibot.js +0 -15
- package/dist/generator/valibot/generator.d.ts +0 -3
- package/dist/generator/valibot/generator.js +0 -14
- package/dist/generator/valibot/index.d.ts +0 -14
- package/dist/generator/valibot/index.js +0 -50
- package/dist/generator/zod/config/index.d.ts +0 -8
- package/dist/generator/zod/config/index.js +0 -14
- package/dist/generator/zod/core/extract-schema.d.ts +0 -7
- package/dist/generator/zod/core/extract-schema.js +0 -243
- package/dist/generator/zod/core.d.ts +0 -5
- package/dist/generator/zod/core.js +0 -39
- package/dist/generator/zod/generator/infer.d.ts +0 -5
- package/dist/generator/zod/generator/infer.js +0 -8
- package/dist/generator/zod/generator/relation-zod-code.d.ts +0 -13
- package/dist/generator/zod/generator/relation-zod-code.js +0 -19
- package/dist/generator/zod/generator/zod-code.d.ts +0 -17
- package/dist/generator/zod/generator/zod-code.js +0 -18
- package/dist/generator/zod/generator/zod.d.ts +0 -16
- package/dist/generator/zod/generator/zod.js +0 -16
- package/dist/generator/zod/generator.d.ts +0 -3
- package/dist/generator/zod/generator.js +0 -14
- package/dist/generator/zod/index.d.ts +0 -15
- package/dist/generator/zod/index.js +0 -54
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -73
- package/dist/shared/config/index.d.ts +0 -13
- package/dist/shared/config/index.js +0 -10
- package/dist/shared/format/index.d.ts +0 -13
- package/dist/shared/format/index.js +0 -24
- package/dist/shared/fs/index.d.ts +0 -7
- package/dist/shared/fs/index.js +0 -16
- package/dist/shared/fsp/index.d.ts +0 -27
- package/dist/shared/fsp/index.js +0 -38
- package/dist/shared/generator/field-definitions.d.ts +0 -12
- package/dist/shared/generator/field-definitions.js +0 -12
- package/dist/shared/helper/ast-parser.d.ts +0 -3
- package/dist/shared/helper/ast-parser.js +0 -202
- package/dist/shared/helper/build-schema-extractor.d.ts +0 -25
- package/dist/shared/helper/build-schema-extractor.js +0 -33
- package/dist/shared/helper/create-extract-field-from-property.d.ts +0 -15
- package/dist/shared/helper/create-extract-field-from-property.js +0 -20
- package/dist/shared/helper/create-extract-fields-from-call-expression.d.ts +0 -14
- package/dist/shared/helper/create-extract-fields-from-call-expression.js +0 -14
- package/dist/shared/helper/create-extract-relation-field-from-property.d.ts +0 -12
- package/dist/shared/helper/create-extract-relation-field-from-property.js +0 -27
- package/dist/shared/helper/extract-schemas.d.ts +0 -133
- package/dist/shared/helper/extract-schemas.js +0 -445
- package/dist/shared/helper/file-writer.d.ts +0 -3
- package/dist/shared/helper/file-writer.js +0 -25
- package/dist/shared/helper/find-object-literal-expression.d.ts +0 -12
- package/dist/shared/helper/find-object-literal-expression.js +0 -31
- package/dist/shared/helper/find-object-literalIn-args.d.ts +0 -2
- package/dist/shared/helper/find-object-literalIn-args.js +0 -8
- package/dist/shared/helper/is-relation-function.d.ts +0 -10
- package/dist/shared/helper/is-relation-function.js +0 -16
- package/dist/shared/types.d.ts +0 -9
- package/dist/shared/types.js +0 -1
- package/dist/shared/utils/capitalize.d.ts +0 -18
- package/dist/shared/utils/capitalize.js +0 -20
- package/dist/shared/utils/compose.d.ts +0 -101
- package/dist/shared/utils/compose.js +0 -124
- package/dist/shared/utils/file.d.ts +0 -92
- package/dist/shared/utils/file.js +0 -177
- package/dist/shared/utils/functional.d.ts +0 -118
- package/dist/shared/utils/functional.js +0 -96
- package/dist/shared/utils/index.d.ts +0 -20
- package/dist/shared/utils/index.js +0 -48
- package/dist/shared/utils/string-utils.d.ts +0 -8
- package/dist/shared/utils/string-utils.js +0 -28
- package/dist/shared/utils/types.d.ts +0 -32
- package/dist/shared/utils/types.js +0 -2
- package/dist/shared/utils/validation-utils.d.ts +0 -8
- package/dist/shared/utils/validation-utils.js +0 -25
- package/dist/src/config/index.d.ts +0 -18
- package/dist/src/config/index.js +0 -13
- package/dist/src/generator/mermaid-er/core/extract-relations.d.ts +0 -8
- package/dist/src/generator/mermaid-er/core/extract-relations.js +0 -12
- package/dist/src/generator/mermaid-er/generator/er-content.d.ts +0 -9
- package/dist/src/generator/mermaid-er/generator/er-content.js +0 -25
- package/dist/src/generator/mermaid-er/generator/index.d.ts +0 -2
- package/dist/src/generator/mermaid-er/generator/index.js +0 -2
- package/dist/src/generator/mermaid-er/generator/relation-line.d.ts +0 -8
- package/dist/src/generator/mermaid-er/generator/relation-line.js +0 -14
- package/dist/src/generator/mermaid-er/index.d.ts +0 -6
- package/dist/src/generator/mermaid-er/index.js +0 -16
- package/dist/src/generator/mermaid-er/relationship/build-relation-line.d.ts +0 -15
- package/dist/src/generator/mermaid-er/relationship/build-relation-line.js +0 -35
- package/dist/src/generator/mermaid-er/types.d.ts +0 -21
- package/dist/src/generator/mermaid-er/types.js +0 -1
- package/dist/src/generator/mermaid-er/validator/index.d.ts +0 -4
- package/dist/src/generator/mermaid-er/validator/index.js +0 -4
- package/dist/src/generator/mermaid-er/validator/is-relationship.d.ts +0 -8
- package/dist/src/generator/mermaid-er/validator/is-relationship.js +0 -9
- package/dist/src/generator/mermaid-er/validator/parse-relation-line.d.ts +0 -13
- package/dist/src/generator/mermaid-er/validator/parse-relation-line.js +0 -21
- package/dist/src/generator/mermaid-er/validator/parse-table-info.d.ts +0 -8
- package/dist/src/generator/mermaid-er/validator/parse-table-info.js +0 -91
- package/dist/src/generator/mermaid-er/validator/remove-duplicate-relations.d.ts +0 -7
- package/dist/src/generator/mermaid-er/validator/remove-duplicate-relations.js +0 -9
- package/dist/src/generator/valibot/generator/infer-input.d.ts +0 -5
- package/dist/src/generator/valibot/generator/infer-input.js +0 -8
- package/dist/src/generator/valibot/generator/valibot-code.d.ts +0 -14
- package/dist/src/generator/valibot/generator/valibot-code.js +0 -16
- package/dist/src/generator/valibot/generator/valibot.d.ts +0 -13
- package/dist/src/generator/valibot/generator/valibot.js +0 -11
- package/dist/src/generator/valibot/index.d.ts +0 -9
- package/dist/src/generator/valibot/index.js +0 -34
- package/dist/src/generator/zod/generator/infer.d.ts +0 -5
- package/dist/src/generator/zod/generator/infer.js +0 -8
- package/dist/src/generator/zod/generator/zod-code.d.ts +0 -16
- package/dist/src/generator/zod/generator/zod-code.js +0 -18
- package/dist/src/generator/zod/generator/zod.d.ts +0 -15
- package/dist/src/generator/zod/generator/zod.js +0 -12
- package/dist/src/generator/zod/index.d.ts +0 -10
- package/dist/src/generator/zod/index.js +0 -40
- package/dist/src/index.d.ts +0 -10
- package/dist/src/index.js +0 -35
- package/dist/src/shared/format/index.d.ts +0 -2
- package/dist/src/shared/format/index.js +0 -10
- package/dist/src/shared/fs/index.d.ts +0 -2
- package/dist/src/shared/fs/index.js +0 -10
- package/dist/src/shared/fsp/index.d.ts +0 -3
- package/dist/src/shared/fsp/index.js +0 -8
- package/dist/src/shared/generator/field-definitions.d.ts +0 -12
- package/dist/src/shared/generator/field-definitions.js +0 -12
- package/dist/src/shared/helper/build-schema-extractor.d.ts +0 -25
- package/dist/src/shared/helper/build-schema-extractor.js +0 -33
- package/dist/src/shared/helper/create-extract-field-from-property.d.ts +0 -15
- package/dist/src/shared/helper/create-extract-field-from-property.js +0 -20
- package/dist/src/shared/helper/create-extract-fields-from-call-expression.d.ts +0 -14
- package/dist/src/shared/helper/create-extract-fields-from-call-expression.js +0 -14
- package/dist/src/shared/helper/create-extract-relation-field-from-property.d.ts +0 -12
- package/dist/src/shared/helper/create-extract-relation-field-from-property.js +0 -27
- package/dist/src/shared/helper/extract-schemas.d.ts +0 -16
- package/dist/src/shared/helper/extract-schemas.js +0 -19
- package/dist/src/shared/helper/find-object-literal-expression.d.ts +0 -12
- package/dist/src/shared/helper/find-object-literal-expression.js +0 -31
- package/dist/src/shared/helper/find-object-literalIn-args.d.ts +0 -2
- package/dist/src/shared/helper/find-object-literalIn-args.js +0 -8
- package/dist/src/shared/helper/is-relation-function.d.ts +0 -10
- package/dist/src/shared/helper/is-relation-function.js +0 -16
- package/dist/src/shared/utils/index.d.ts +0 -33
- package/dist/src/shared/utils/index.js +0 -61
- package/dist/utils/index.d.ts +0 -144
- package/dist/utils/index.js +0 -250
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1615 @@
|
|
|
1
|
+
import { config } from "./config/index.mjs";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { format } from "prettier";
|
|
5
|
+
import fsp from "node:fs/promises";
|
|
6
|
+
import { Node, Project } from "ts-morph";
|
|
7
|
+
import { generateDBMLContent, makeCapitalized, makeCapitalized as makeCapitalized$1, makeCleanedCommentLines, makeValibotInfer, makeValibotObject, makeZodInfer, makeZodObject, mapDrizzleType } from "utils-lab";
|
|
8
|
+
import { Resvg } from "@resvg/resvg-js";
|
|
9
|
+
import { run } from "@softwaretechnik/dbml-renderer";
|
|
10
|
+
|
|
11
|
+
//#region src/shared/format/index.ts
|
|
12
|
+
/**
|
|
13
|
+
* Formats TypeScript source with Prettier.
|
|
14
|
+
*
|
|
15
|
+
* @param code - Source code to format.
|
|
16
|
+
* @returns A `Result` containing the formatted code or an error message.
|
|
17
|
+
*/
|
|
18
|
+
async function fmt(code) {
|
|
19
|
+
try {
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
value: await format(code, {
|
|
23
|
+
parser: "typescript",
|
|
24
|
+
printWidth: 100,
|
|
25
|
+
singleQuote: true,
|
|
26
|
+
semi: false
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
error: e instanceof Error ? e.message : String(e)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/shared/fsp/index.ts
|
|
39
|
+
/**
|
|
40
|
+
* Creates a directory if it does not already exist.
|
|
41
|
+
*
|
|
42
|
+
* @param dir - Directory path to create.
|
|
43
|
+
* @returns A `Result` that is `ok` on success, otherwise an error message.
|
|
44
|
+
*/
|
|
45
|
+
async function mkdir(dir) {
|
|
46
|
+
try {
|
|
47
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
48
|
+
return {
|
|
49
|
+
ok: true,
|
|
50
|
+
value: void 0
|
|
51
|
+
};
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
error: e instanceof Error ? e.message : String(e)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Writes UTF-8 text to a file, creating it if necessary.
|
|
61
|
+
*
|
|
62
|
+
* @param path - File path to write.
|
|
63
|
+
* @param data - Text data to write.
|
|
64
|
+
* @returns A `Result` that is `ok` on success, otherwise an error message.
|
|
65
|
+
*/
|
|
66
|
+
async function writeFile(path, data) {
|
|
67
|
+
try {
|
|
68
|
+
await fsp.writeFile(path, data, "utf-8");
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
value: void 0
|
|
72
|
+
};
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
error: e instanceof Error ? e.message : String(e)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Writes binary data to a file, creating it if necessary.
|
|
82
|
+
*
|
|
83
|
+
* @param path - File path to write.
|
|
84
|
+
* @param data - Binary data to write.
|
|
85
|
+
* @returns A `Result` that is `ok` on success, otherwise an error message.
|
|
86
|
+
*/
|
|
87
|
+
async function writeFileBinary(path, data) {
|
|
88
|
+
try {
|
|
89
|
+
await fsp.writeFile(path, data);
|
|
90
|
+
return {
|
|
91
|
+
ok: true,
|
|
92
|
+
value: void 0
|
|
93
|
+
};
|
|
94
|
+
} catch (e) {
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
error: e instanceof Error ? e.message : String(e)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/utils/index.ts
|
|
104
|
+
/**
|
|
105
|
+
* Check if a string starts with a prefix.
|
|
106
|
+
*
|
|
107
|
+
* @param str - The input string.
|
|
108
|
+
* @param prefix - The prefix to check.
|
|
109
|
+
* @returns True if string starts with prefix.
|
|
110
|
+
*/
|
|
111
|
+
function startsWith(str, prefix) {
|
|
112
|
+
return str.indexOf(prefix) === 0;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Trim whitespace from string.
|
|
116
|
+
*
|
|
117
|
+
* @param str - The input string.
|
|
118
|
+
* @returns Trimmed string.
|
|
119
|
+
*/
|
|
120
|
+
function trimString(str) {
|
|
121
|
+
return str.trim();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Clean comment lines by removing triple slash prefix and trimming.
|
|
125
|
+
* Uses makeCleanedCommentLines from utils-lab.
|
|
126
|
+
*
|
|
127
|
+
* @param commentLines - Raw comment lines
|
|
128
|
+
* @returns Cleaned comment lines
|
|
129
|
+
*/
|
|
130
|
+
function cleanCommentLines(commentLines) {
|
|
131
|
+
return makeCleanedCommentLines(commentLines);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Extract object type from comment lines.
|
|
135
|
+
*
|
|
136
|
+
* @param cleaned - Cleaned comment lines
|
|
137
|
+
* @param tag - The tag to look for
|
|
138
|
+
* @returns The object type if found
|
|
139
|
+
*/
|
|
140
|
+
function extractObjectType(cleaned, tag) {
|
|
141
|
+
const tagWithoutAt = tag.slice(1);
|
|
142
|
+
const objectTypeLine = cleaned.find((line) => line.includes(`${tagWithoutAt}strictObject`) || line.includes(`${tagWithoutAt}looseObject`));
|
|
143
|
+
if (!objectTypeLine) return void 0;
|
|
144
|
+
if (objectTypeLine.includes("strictObject")) return "strict";
|
|
145
|
+
if (objectTypeLine.includes("looseObject")) return "loose";
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Extract definition from comment lines.
|
|
149
|
+
*
|
|
150
|
+
* @param cleaned - Cleaned comment lines
|
|
151
|
+
* @param tag - The tag to look for
|
|
152
|
+
* @returns The definition string
|
|
153
|
+
*/
|
|
154
|
+
function extractDefinition(cleaned, tag) {
|
|
155
|
+
const definitionLine = cleaned.find((line) => line.startsWith(tag) && !line.includes("strictObject") && !line.includes("looseObject"));
|
|
156
|
+
if (!definitionLine) return "";
|
|
157
|
+
const withoutAt = definitionLine.startsWith("@") ? definitionLine.substring(1) : definitionLine;
|
|
158
|
+
if (tag === "@a." || tag === "@e.") {
|
|
159
|
+
const prefix = tag.substring(1);
|
|
160
|
+
return withoutAt.startsWith(prefix) ? withoutAt.substring(prefix.length) : withoutAt;
|
|
161
|
+
}
|
|
162
|
+
return withoutAt;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Extract description from comment lines.
|
|
166
|
+
*
|
|
167
|
+
* @param cleaned - Cleaned comment lines
|
|
168
|
+
* @returns The description if found
|
|
169
|
+
*/
|
|
170
|
+
function extractDescription(cleaned) {
|
|
171
|
+
const descriptionLines = cleaned.filter((line) => !(line.includes("@z.") || line.includes("@v.") || line.includes("@a.") || line.includes("@e.") || line.includes("@relation.")));
|
|
172
|
+
return descriptionLines.length > 0 ? descriptionLines.join(" ") : void 0;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Parse field comments and extract definition line and description.
|
|
176
|
+
*
|
|
177
|
+
* @param commentLines - Raw comment lines (e.g., from source text)
|
|
178
|
+
* @param tag - The tag to look for (e.g., '@v.', '@z.', '@a.', or '@e.')
|
|
179
|
+
* @returns Parsed definition and description
|
|
180
|
+
*/
|
|
181
|
+
function parseFieldComments(commentLines, tag) {
|
|
182
|
+
const cleaned = cleanCommentLines(commentLines);
|
|
183
|
+
const objectType = extractObjectType(cleaned, tag);
|
|
184
|
+
return {
|
|
185
|
+
definition: extractDefinition(cleaned, tag),
|
|
186
|
+
description: extractDescription(cleaned),
|
|
187
|
+
objectType
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Process a single line during comment extraction.
|
|
192
|
+
*
|
|
193
|
+
* @param acc - The accumulator
|
|
194
|
+
* @param line - The line to process
|
|
195
|
+
* @returns Updated accumulator
|
|
196
|
+
*/
|
|
197
|
+
function processCommentLine(acc, line) {
|
|
198
|
+
if (acc.shouldStop) return acc;
|
|
199
|
+
if (line.startsWith("///")) return {
|
|
200
|
+
commentLines: [line, ...acc.commentLines],
|
|
201
|
+
shouldStop: false
|
|
202
|
+
};
|
|
203
|
+
if (line === "") return acc;
|
|
204
|
+
return {
|
|
205
|
+
commentLines: acc.commentLines,
|
|
206
|
+
shouldStop: true
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Extract field comments from source text.
|
|
211
|
+
*
|
|
212
|
+
* @param sourceText - The source text to extract comments from.
|
|
213
|
+
* @param fieldStartPos - The position of the field in the source text.
|
|
214
|
+
* @returns An array of comment lines.
|
|
215
|
+
*/
|
|
216
|
+
function extractFieldComments(sourceText, fieldStartPos) {
|
|
217
|
+
return sourceText.substring(0, fieldStartPos).split("\n").map((line, index) => ({
|
|
218
|
+
line: line.trim(),
|
|
219
|
+
index
|
|
220
|
+
})).reverse().reduce((acc, { line }) => processCommentLine(acc, line), {
|
|
221
|
+
commentLines: [],
|
|
222
|
+
shouldStop: false
|
|
223
|
+
}).commentLines;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Creates `z.infer` type for the specified model.
|
|
227
|
+
* Uses makeZodInfer from utils-lab with capitalized model name.
|
|
228
|
+
*
|
|
229
|
+
* @param name - The model name
|
|
230
|
+
* @returns The generated TypeScript type definition line using Zod.
|
|
231
|
+
*/
|
|
232
|
+
function infer(name) {
|
|
233
|
+
return makeZodInfer(makeCapitalized(name));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Creates `v.InferInput` type for the specified model.
|
|
237
|
+
* Uses makeValibotInfer from utils-lab with capitalized model name.
|
|
238
|
+
*
|
|
239
|
+
* @param name - The model name
|
|
240
|
+
* @returns The generated TypeScript type definition line using Valibot.
|
|
241
|
+
*/
|
|
242
|
+
function inferInput(name) {
|
|
243
|
+
return makeValibotInfer(makeCapitalized(name));
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* @param schema
|
|
247
|
+
* @returns
|
|
248
|
+
*/
|
|
249
|
+
function fieldDefinitions(schema, comment) {
|
|
250
|
+
return schema.fields.map(({ name, definition, description }) => {
|
|
251
|
+
return `${description && comment ? `/**\n* ${description}\n*/\n` : ""}${name}:${definition}`;
|
|
252
|
+
}).join(",\n");
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Creates ArkType infer type for the specified model.
|
|
256
|
+
*
|
|
257
|
+
* @param name - The model name
|
|
258
|
+
* @returns The generated TypeScript type definition line using ArkType.
|
|
259
|
+
*/
|
|
260
|
+
function inferArktype(name) {
|
|
261
|
+
const capitalized = makeCapitalized(name);
|
|
262
|
+
return `export type ${capitalized} = typeof ${capitalized}Schema.infer`;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Creates Effect Schema infer type for the specified model.
|
|
266
|
+
*
|
|
267
|
+
* @param name - The model name
|
|
268
|
+
* @returns The generated TypeScript type definition line using Effect Schema.
|
|
269
|
+
*/
|
|
270
|
+
function inferEffect(name) {
|
|
271
|
+
const capitalized = makeCapitalized(name);
|
|
272
|
+
return `export type ${capitalized} = Schema.Schema.Type<typeof ${capitalized}Schema>`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/shared/helper/extract-schemas.ts
|
|
277
|
+
/**
|
|
278
|
+
* Generates relation definition based on function name and reference table.
|
|
279
|
+
*
|
|
280
|
+
* @param fnName - The relation function name ('many' or 'one')
|
|
281
|
+
* @param refTable - The referenced table name
|
|
282
|
+
* @param prefix - Schema prefix for validation library
|
|
283
|
+
* @returns The generated relation definition string
|
|
284
|
+
*/
|
|
285
|
+
function generateRelationDefinition(fnName, refTable, prefix) {
|
|
286
|
+
const schema = `${makeCapitalized$1(refTable)}Schema`;
|
|
287
|
+
if (fnName === "many") {
|
|
288
|
+
if (prefix === "type") return `${schema}.array()`;
|
|
289
|
+
if (prefix === "Schema") return `Schema.Array(${schema})`;
|
|
290
|
+
return `${prefix}.array(${schema})`;
|
|
291
|
+
}
|
|
292
|
+
if (fnName === "one") return schema;
|
|
293
|
+
return "";
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Processes arrow function body to find object literal expression.
|
|
297
|
+
*
|
|
298
|
+
* @param body - The arrow function body node
|
|
299
|
+
* @returns The found object literal expression, or null if not found
|
|
300
|
+
*/
|
|
301
|
+
function processArrowFunctionBody(body) {
|
|
302
|
+
if (Node.isObjectLiteralExpression(body)) return body;
|
|
303
|
+
if (Node.isParenthesizedExpression(body)) return findObjectLiteralExpression(body.getExpression());
|
|
304
|
+
if (Node.isBlock(body)) {
|
|
305
|
+
const ret = body.getStatements().find(Node.isReturnStatement);
|
|
306
|
+
if (ret && Node.isReturnStatement(ret)) {
|
|
307
|
+
const re = ret.getExpression();
|
|
308
|
+
return re && Node.isObjectLiteralExpression(re) ? re : null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Recursively extracts an `ObjectLiteralExpression` from a given AST node.
|
|
315
|
+
*
|
|
316
|
+
* @param expr - The root `Node` to search for object literals
|
|
317
|
+
* @returns The found `ObjectLiteralExpression`, or `null` if not found
|
|
318
|
+
*/
|
|
319
|
+
function findObjectLiteralExpression(expr) {
|
|
320
|
+
if (Node.isObjectLiteralExpression(expr)) return expr;
|
|
321
|
+
if (Node.isParenthesizedExpression(expr)) return findObjectLiteralExpression(expr.getExpression());
|
|
322
|
+
if (Node.isArrowFunction(expr)) return processArrowFunctionBody(expr.getBody());
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Finds an object literal expression in call expression arguments.
|
|
327
|
+
*
|
|
328
|
+
* @param call - The call expression to search for object literals in its arguments
|
|
329
|
+
* @param finder - Function to find object literal in a node
|
|
330
|
+
* @returns The found object literal, or `null` if not found in any argument
|
|
331
|
+
*/
|
|
332
|
+
function findObjectLiteralInArgs(call, finder) {
|
|
333
|
+
for (const arg of call.getArguments()) {
|
|
334
|
+
const obj = finder(arg);
|
|
335
|
+
if (obj) return obj;
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Determines whether a given `CallExpression` is a relation-related function call.
|
|
341
|
+
*
|
|
342
|
+
* @param callExpr - The call expression node to check for relation functions
|
|
343
|
+
* @returns `true` if the function is a relation function; otherwise, `false`
|
|
344
|
+
*/
|
|
345
|
+
function isRelationFunctionCall(callExpr) {
|
|
346
|
+
const expression = callExpr.getExpression();
|
|
347
|
+
if (!Node.isIdentifier(expression)) return false;
|
|
348
|
+
const functionName = expression.getText();
|
|
349
|
+
return functionName === "relations" || functionName.includes("relation");
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Creates a field extractor function using a custom parseFieldComments implementation.
|
|
353
|
+
*
|
|
354
|
+
* @param parseFieldComments - A function that parses comment lines into { definition, description, objectType }
|
|
355
|
+
* @returns A property node extractor function
|
|
356
|
+
*/
|
|
357
|
+
function createExtractFieldFromProperty(parseFieldComments) {
|
|
358
|
+
return (property, sourceText) => {
|
|
359
|
+
if (!Node.isPropertyAssignment(property)) return null;
|
|
360
|
+
const name = property.getName();
|
|
361
|
+
if (!name) return null;
|
|
362
|
+
const { definition, description } = parseFieldComments(extractFieldComments(sourceText, property.getStart()));
|
|
363
|
+
return {
|
|
364
|
+
name,
|
|
365
|
+
definition,
|
|
366
|
+
description
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Creates a relation field extractor function.
|
|
372
|
+
*
|
|
373
|
+
* @param parseFieldComments - Function to parse field comments
|
|
374
|
+
* @param prefix - Schema prefix for validation library
|
|
375
|
+
* @returns Function that extracts relation fields from property
|
|
376
|
+
*/
|
|
377
|
+
function createExtractRelationFieldFromProperty(parseFieldComments, prefix) {
|
|
378
|
+
return (property, sourceText) => {
|
|
379
|
+
if (!Node.isPropertyAssignment(property)) return null;
|
|
380
|
+
const name = property.getName();
|
|
381
|
+
if (!name) return null;
|
|
382
|
+
const init = property.getInitializer();
|
|
383
|
+
if (!Node.isCallExpression(init)) return {
|
|
384
|
+
name,
|
|
385
|
+
definition: "",
|
|
386
|
+
description: void 0
|
|
387
|
+
};
|
|
388
|
+
const expr = init.getExpression();
|
|
389
|
+
if (!Node.isIdentifier(expr)) return {
|
|
390
|
+
name,
|
|
391
|
+
definition: "",
|
|
392
|
+
description: void 0
|
|
393
|
+
};
|
|
394
|
+
const fnName = expr.getText();
|
|
395
|
+
const args = init.getArguments();
|
|
396
|
+
if (!(args.length && Node.isIdentifier(args[0]))) return {
|
|
397
|
+
name,
|
|
398
|
+
definition: "",
|
|
399
|
+
description: void 0
|
|
400
|
+
};
|
|
401
|
+
const definition = generateRelationDefinition(fnName, args[0].getText(), prefix);
|
|
402
|
+
const { description } = parseFieldComments(extractFieldComments(sourceText, property.getStart()));
|
|
403
|
+
return {
|
|
404
|
+
name,
|
|
405
|
+
definition,
|
|
406
|
+
description
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Extracts fields from object literal properties using appropriate extractor.
|
|
412
|
+
*
|
|
413
|
+
* @param properties - Array of object literal properties
|
|
414
|
+
* @param isRelation - Whether this is a relation function call
|
|
415
|
+
* @param extractFieldFromProperty - Function to extract regular fields
|
|
416
|
+
* @param extractRelationFieldFromProperty - Function to extract relation fields
|
|
417
|
+
* @param sourceText - The source text for comment extraction
|
|
418
|
+
* @returns Array of extracted field results
|
|
419
|
+
*/
|
|
420
|
+
function extractFieldsFromProperties(properties, isRelation, extractFieldFromProperty, extractRelationFieldFromProperty, sourceText) {
|
|
421
|
+
return properties.map((prop) => isRelation ? extractRelationFieldFromProperty(prop, sourceText) : extractFieldFromProperty(prop, sourceText)).filter((field) => field !== null);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Creates a field extractor for call expressions with customizable strategies.
|
|
425
|
+
*
|
|
426
|
+
* @param extractFieldFromProperty - Function to extract field from property
|
|
427
|
+
* @param extractRelationFieldFromProperty - Function to extract relation field from property
|
|
428
|
+
* @param findObjectLiteralExpression - Function to find object literal expression
|
|
429
|
+
* @param findObjectLiteralInArgs - Function to find object literal in call arguments
|
|
430
|
+
* @param isRelationFunctionCall - Function to check if call is relation function
|
|
431
|
+
* @returns Function that extracts fields from call expression
|
|
432
|
+
*/
|
|
433
|
+
function createExtractFieldsFromCallExpression(extractFieldFromProperty, extractRelationFieldFromProperty, findObjectLiteralExpression, findObjectLiteralInArgs, isRelationFunctionCall) {
|
|
434
|
+
return (callExpr, sourceText) => {
|
|
435
|
+
const objectLiteral = findObjectLiteralInArgs(callExpr, findObjectLiteralExpression);
|
|
436
|
+
if (!objectLiteral) return [];
|
|
437
|
+
const isRelation = isRelationFunctionCall(callExpr);
|
|
438
|
+
return extractFieldsFromProperties(objectLiteral.getProperties(), isRelation, extractFieldFromProperty, extractRelationFieldFromProperty, sourceText);
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Creates a schema extractor from customizable strategies.
|
|
443
|
+
*
|
|
444
|
+
* @param extractFieldsFromCall - Function to extract fields from a call expression
|
|
445
|
+
* @param extractFieldFromProperty - Function to extract a single field from an object literal property
|
|
446
|
+
* @param parseFieldComments - Function to parse field comments with object type support
|
|
447
|
+
* @param commentPrefix - The comment prefix to use for parsing
|
|
448
|
+
* @returns A function that extracts a schema from a variable declaration node
|
|
449
|
+
*/
|
|
450
|
+
function buildSchemaExtractor(extractFieldsFromCall, extractFieldFromProperty, parseFieldComments, commentPrefix) {
|
|
451
|
+
return (variableStatement, sourceText, originalSourceCode) => {
|
|
452
|
+
if (!Node.isVariableStatement(variableStatement)) return null;
|
|
453
|
+
const declarations = variableStatement.getDeclarations();
|
|
454
|
+
if (declarations.length === 0) return null;
|
|
455
|
+
const declaration = declarations[0];
|
|
456
|
+
const name = declaration.getName();
|
|
457
|
+
if (!name) return null;
|
|
458
|
+
const statementStart = variableStatement.getStart();
|
|
459
|
+
const originalSourceLines = originalSourceCode.split("\n");
|
|
460
|
+
const commentLines = [];
|
|
461
|
+
let lineNumber = 0;
|
|
462
|
+
let charCount = 0;
|
|
463
|
+
for (let i = 0; i < originalSourceLines.length; i++) {
|
|
464
|
+
if (charCount >= statementStart) {
|
|
465
|
+
lineNumber = i;
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
charCount += originalSourceLines[i].length + 1;
|
|
469
|
+
}
|
|
470
|
+
for (let i = lineNumber - 1; i >= 0; i--) {
|
|
471
|
+
const line = originalSourceLines[i];
|
|
472
|
+
const trimmedLine = trimString(line);
|
|
473
|
+
if (trimmedLine === "") continue;
|
|
474
|
+
if (startsWith(trimmedLine, "///")) commentLines.unshift(line);
|
|
475
|
+
else break;
|
|
476
|
+
}
|
|
477
|
+
const { objectType } = parseFieldComments(commentLines, commentPrefix);
|
|
478
|
+
const initializer = declaration.getInitializer();
|
|
479
|
+
if (Node.isCallExpression(initializer)) {
|
|
480
|
+
if (isRelationFunctionCall(initializer)) return null;
|
|
481
|
+
return {
|
|
482
|
+
name,
|
|
483
|
+
fields: extractFieldsFromCall(initializer, sourceText),
|
|
484
|
+
objectType
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
if (Node.isObjectLiteralExpression(initializer)) return {
|
|
488
|
+
name,
|
|
489
|
+
fields: initializer.getProperties().map((prop) => extractFieldFromProperty(prop, sourceText)).filter((field) => field !== null),
|
|
490
|
+
objectType
|
|
491
|
+
};
|
|
492
|
+
return {
|
|
493
|
+
name,
|
|
494
|
+
fields: [],
|
|
495
|
+
objectType
|
|
496
|
+
};
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Extracts schemas from TypeScript source code using AST analysis.
|
|
501
|
+
*
|
|
502
|
+
* This function processes exported variable declarations to extract table schemas
|
|
503
|
+
* with their field definitions and comments. It supports both Zod and Valibot schema extraction.
|
|
504
|
+
*
|
|
505
|
+
* @param lines - Array of source code lines to process
|
|
506
|
+
* @param library - The validation library to extract schemas for ('zod' or 'valibot')
|
|
507
|
+
* @returns Array of extracted schemas with field definitions
|
|
508
|
+
*
|
|
509
|
+
* @example
|
|
510
|
+
* ```typescript
|
|
511
|
+
* // For Zod schemas
|
|
512
|
+
* const zodSchemas = extractSchemas(sourceLines, 'zod')
|
|
513
|
+
*
|
|
514
|
+
* // For Valibot schemas
|
|
515
|
+
* const valibotSchemas = extractSchemas(sourceLines, 'valibot')
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
518
|
+
function extractSchemas(lines, library) {
|
|
519
|
+
const sourceCode = lines.join("\n");
|
|
520
|
+
const sourceFile = new Project({
|
|
521
|
+
useInMemoryFileSystem: true,
|
|
522
|
+
compilerOptions: {
|
|
523
|
+
allowJs: true,
|
|
524
|
+
skipLibCheck: true
|
|
525
|
+
}
|
|
526
|
+
}).createSourceFile("temp.ts", sourceCode);
|
|
527
|
+
const sourceText = sourceFile.getFullText();
|
|
528
|
+
const commentPrefix = library === "zod" ? "@z." : library === "valibot" ? "@v." : library === "arktype" ? "@a." : "@e.";
|
|
529
|
+
const schemaPrefix = library === "zod" ? "z" : library === "valibot" ? "v" : library === "arktype" ? "type" : "Schema";
|
|
530
|
+
const extractField = createExtractFieldFromProperty((lines) => parseFieldComments(lines, commentPrefix));
|
|
531
|
+
const extractSchema = buildSchemaExtractor(createExtractFieldsFromCallExpression(extractField, createExtractRelationFieldFromProperty((lines) => parseFieldComments(lines, commentPrefix), schemaPrefix), findObjectLiteralExpression, findObjectLiteralInArgs, isRelationFunctionCall), extractField, parseFieldComments, commentPrefix);
|
|
532
|
+
return sourceFile.getVariableStatements().filter((stmt) => stmt.hasExportKeyword()).map((stmt) => extractSchema(stmt, sourceText, sourceCode)).filter((schema) => schema !== null);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Extracts relation schemas from `relations(...)` declarations using AST analysis.
|
|
536
|
+
*
|
|
537
|
+
* This returns entries like `userRelations` and `postRelations` with fields
|
|
538
|
+
* resolved to either `z.array(OtherSchema)` / `v.array(OtherSchema)` or direct
|
|
539
|
+
* `OtherSchema` based on `many`/`one`.
|
|
540
|
+
*
|
|
541
|
+
* Note: Base table schemas are not included here; use `extractSchemas` for those.
|
|
542
|
+
*/
|
|
543
|
+
function extractRelationSchemas(lines, library) {
|
|
544
|
+
const sourceCode = lines.join("\n");
|
|
545
|
+
const sourceFile = new Project({
|
|
546
|
+
useInMemoryFileSystem: true,
|
|
547
|
+
compilerOptions: {
|
|
548
|
+
allowJs: true,
|
|
549
|
+
skipLibCheck: true
|
|
550
|
+
}
|
|
551
|
+
}).createSourceFile("temp.ts", sourceCode);
|
|
552
|
+
const sourceText = sourceFile.getFullText();
|
|
553
|
+
const commentPrefix = library === "zod" ? "@z." : library === "valibot" ? "@v." : library === "arktype" ? "@a." : "@e.";
|
|
554
|
+
const schemaPrefix = library === "zod" ? "z" : library === "valibot" ? "v" : library === "arktype" ? "type" : "Schema";
|
|
555
|
+
const baseSchemas = extractSchemas(lines, library);
|
|
556
|
+
const baseSchemaMap = new Map(baseSchemas.map((schema) => [schema.name, schema.objectType]));
|
|
557
|
+
const extractFieldsFromCall = createExtractFieldsFromCallExpression(createExtractFieldFromProperty((lines) => parseFieldComments(lines, commentPrefix)), createExtractRelationFieldFromProperty((lines) => parseFieldComments(lines, commentPrefix), schemaPrefix), findObjectLiteralExpression, findObjectLiteralInArgs, isRelationFunctionCall);
|
|
558
|
+
function extract(declaration) {
|
|
559
|
+
if (!Node.isVariableDeclaration(declaration)) return null;
|
|
560
|
+
const name = declaration.getName();
|
|
561
|
+
if (!name) return null;
|
|
562
|
+
const initializer = declaration.getInitializer();
|
|
563
|
+
if (!Node.isCallExpression(initializer)) return null;
|
|
564
|
+
if (!isRelationFunctionCall(initializer)) return null;
|
|
565
|
+
const relArgs = initializer.getArguments();
|
|
566
|
+
const baseIdentifier = relArgs.length && Node.isIdentifier(relArgs[0]) ? relArgs[0] : void 0;
|
|
567
|
+
if (!baseIdentifier) return null;
|
|
568
|
+
const baseName = baseIdentifier.getText();
|
|
569
|
+
return {
|
|
570
|
+
name,
|
|
571
|
+
baseName,
|
|
572
|
+
fields: extractFieldsFromCall(initializer, sourceText),
|
|
573
|
+
objectType: baseSchemaMap.get(baseName)
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
return sourceFile.getVariableStatements().filter((stmt) => stmt.hasExportKeyword()).flatMap((stmt) => stmt.getDeclarations()).map((decl) => extract(decl)).filter((schema) => schema !== null);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
//#endregion
|
|
580
|
+
//#region src/generator/arktype/generator/arktype.ts
|
|
581
|
+
/**
|
|
582
|
+
* Generates an ArkType schema for a given schema and config.
|
|
583
|
+
*
|
|
584
|
+
* @param schema - The schema to generate code for.
|
|
585
|
+
* @param comment - Whether to include comments in the generated code.
|
|
586
|
+
* @returns The generated ArkType schema.
|
|
587
|
+
*/
|
|
588
|
+
function arktype(schema, comment) {
|
|
589
|
+
const inner = fieldDefinitions(schema, comment);
|
|
590
|
+
return `export const ${makeCapitalized(schema.name)}Schema = type({${inner}})`;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region src/generator/arktype/generator/arktype-code.ts
|
|
595
|
+
/**
|
|
596
|
+
* Generates ArkType code for a given schema and config.
|
|
597
|
+
*
|
|
598
|
+
* @param schema - The schema to generate code for.
|
|
599
|
+
* @param comment - Whether to include comments in the generated code.
|
|
600
|
+
* @param type - Whether to include type information in the generated code.
|
|
601
|
+
* @returns The generated ArkType code.
|
|
602
|
+
*/
|
|
603
|
+
function arktypeCode(schema, comment, type) {
|
|
604
|
+
const arktypeSchema = arktype(schema, comment);
|
|
605
|
+
if (type) return `${arktypeSchema}\n\n${inferArktype(schema.name)}\n`;
|
|
606
|
+
return `${arktypeSchema}\n`;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
//#endregion
|
|
610
|
+
//#region src/generator/arktype/index.ts
|
|
611
|
+
/**
|
|
612
|
+
* Generate ArkType schema
|
|
613
|
+
* @param code - The code to generate ArkType schema from
|
|
614
|
+
* @param output - The output file path
|
|
615
|
+
* @param comment - Whether to include comments in the generated code
|
|
616
|
+
* @param type - Whether to include type information in the generated code
|
|
617
|
+
*/
|
|
618
|
+
async function sizukuArktype(code, output, comment, type) {
|
|
619
|
+
const arktypeGeneratedCode = [
|
|
620
|
+
`import { type } from 'arktype'`,
|
|
621
|
+
"",
|
|
622
|
+
...extractSchemas(code, "arktype").map((schema) => arktypeCode(schema, comment ?? false, type ?? false))
|
|
623
|
+
].join("\n");
|
|
624
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
625
|
+
if (!mkdirResult.ok) return {
|
|
626
|
+
ok: false,
|
|
627
|
+
error: mkdirResult.error
|
|
628
|
+
};
|
|
629
|
+
const fmtResult = await fmt(arktypeGeneratedCode);
|
|
630
|
+
if (!fmtResult.ok) return {
|
|
631
|
+
ok: false,
|
|
632
|
+
error: fmtResult.error
|
|
633
|
+
};
|
|
634
|
+
const writeFileResult = await writeFile(output, fmtResult.value);
|
|
635
|
+
if (!writeFileResult.ok) return {
|
|
636
|
+
ok: false,
|
|
637
|
+
error: writeFileResult.error
|
|
638
|
+
};
|
|
639
|
+
return {
|
|
640
|
+
ok: true,
|
|
641
|
+
value: void 0
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
//#endregion
|
|
646
|
+
//#region src/generator/mermaid-er/validator/index.ts
|
|
647
|
+
/**
|
|
648
|
+
* Create a unique key for relation deduplication.
|
|
649
|
+
*/
|
|
650
|
+
function relationKey(r) {
|
|
651
|
+
return `${r.fromModel}.${r.fromField}->${r.toModel}.${r.toField}`;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Extract base builder name from expression.
|
|
655
|
+
*
|
|
656
|
+
* @param expr - The expression to extract name from
|
|
657
|
+
* @returns The base builder name
|
|
658
|
+
*/
|
|
659
|
+
function baseBuilderName(expr) {
|
|
660
|
+
if (Node.isIdentifier(expr)) return expr.getText();
|
|
661
|
+
if (Node.isCallExpression(expr) || Node.isPropertyAccessExpression(expr)) return baseBuilderName(expr.getExpression());
|
|
662
|
+
return "";
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Type guard for FieldInfo.
|
|
666
|
+
*
|
|
667
|
+
* @param v - The value to check
|
|
668
|
+
* @returns True if value is FieldInfo
|
|
669
|
+
*/
|
|
670
|
+
function isFieldInfo(v) {
|
|
671
|
+
return v !== null;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Extract key type based on field definition.
|
|
675
|
+
*
|
|
676
|
+
* @param initText - The initializer text
|
|
677
|
+
* @returns The key type (PK, FK, or null)
|
|
678
|
+
*/
|
|
679
|
+
function extractKeyType(initText) {
|
|
680
|
+
if (initText.includes(".primaryKey()")) return "PK";
|
|
681
|
+
if (initText.includes(".references(")) return "FK";
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Find immediate comment for a field.
|
|
686
|
+
*
|
|
687
|
+
* @param code - The source code lines
|
|
688
|
+
* @param lineIdx - The line index of the field
|
|
689
|
+
* @returns The immediate comment or empty string
|
|
690
|
+
*/
|
|
691
|
+
function findImmediateComment(code, lineIdx) {
|
|
692
|
+
return code.slice(0, lineIdx).reverse().find((line) => {
|
|
693
|
+
const t = line.trim();
|
|
694
|
+
return t.startsWith("///") && !t.includes("@z.") && !t.includes("@v.") && !t.includes("@a.") && !t.includes("@e.") && !t.includes("@relation");
|
|
695
|
+
})?.replace(/^\s*\/\/\/\s*/, "") ?? "";
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Extract reference info from a .references() call.
|
|
699
|
+
*
|
|
700
|
+
* @param initExpr - The call expression to analyze
|
|
701
|
+
* @returns The reference info or null
|
|
702
|
+
*/
|
|
703
|
+
function extractReferenceInfo(initExpr) {
|
|
704
|
+
const match = initExpr.getText().match(/\.references\(\s*\(\)\s*=>\s*(\w+)\.(\w+)\s*\)/);
|
|
705
|
+
if (match) return {
|
|
706
|
+
referencedTable: match[1],
|
|
707
|
+
referencedField: match[2]
|
|
708
|
+
};
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Check if field is required (notNull).
|
|
713
|
+
*
|
|
714
|
+
* @param initText - The initializer text
|
|
715
|
+
* @returns True if field is required
|
|
716
|
+
*/
|
|
717
|
+
function isFieldRequired(initText) {
|
|
718
|
+
return initText.includes(".notNull()");
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Extract field info from property assignment.
|
|
722
|
+
*
|
|
723
|
+
* @param prop - The property assignment node
|
|
724
|
+
* @param code - The source code lines
|
|
725
|
+
* @returns Field info or null
|
|
726
|
+
*/
|
|
727
|
+
function extractFieldInfo(prop, code) {
|
|
728
|
+
const keyNode = prop.getNameNode();
|
|
729
|
+
if (!Node.isIdentifier(keyNode)) return null;
|
|
730
|
+
const fieldName = keyNode.getText();
|
|
731
|
+
const initExpr = prop.getInitializer();
|
|
732
|
+
if (!(initExpr && Node.isCallExpression(initExpr))) return null;
|
|
733
|
+
const fieldType = baseBuilderName(initExpr);
|
|
734
|
+
const initText = initExpr.getText();
|
|
735
|
+
const immediateComment = findImmediateComment(code, prop.getStartLineNumber() - 1);
|
|
736
|
+
return {
|
|
737
|
+
name: fieldName,
|
|
738
|
+
type: fieldType,
|
|
739
|
+
keyType: extractKeyType(initText),
|
|
740
|
+
description: immediateComment || null
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Extract relation info from a property assignment with .references().
|
|
745
|
+
*
|
|
746
|
+
* @param prop - The property assignment node
|
|
747
|
+
* @param tableName - The name of the current table
|
|
748
|
+
* @returns Relation info or null
|
|
749
|
+
*/
|
|
750
|
+
function extractRelationFromField(prop, tableName) {
|
|
751
|
+
const keyNode = prop.getNameNode();
|
|
752
|
+
if (!Node.isIdentifier(keyNode)) return null;
|
|
753
|
+
const fieldName = keyNode.getText();
|
|
754
|
+
const initExpr = prop.getInitializer();
|
|
755
|
+
if (!(initExpr && Node.isCallExpression(initExpr))) return null;
|
|
756
|
+
const initText = initExpr.getText();
|
|
757
|
+
if (!initText.includes(".references(")) return null;
|
|
758
|
+
const refInfo = extractReferenceInfo(initExpr);
|
|
759
|
+
if (!refInfo) return null;
|
|
760
|
+
const isRequired = isFieldRequired(initText);
|
|
761
|
+
return {
|
|
762
|
+
fromModel: refInfo.referencedTable,
|
|
763
|
+
toModel: tableName,
|
|
764
|
+
fromField: refInfo.referencedField,
|
|
765
|
+
toField: fieldName,
|
|
766
|
+
isRequired
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Extract relations from foreignKey() constraints in the third argument.
|
|
771
|
+
*
|
|
772
|
+
* Pattern:
|
|
773
|
+
* foreignKey({
|
|
774
|
+
* columns: [TableName.fieldName],
|
|
775
|
+
* foreignColumns: [OtherTable.fieldName],
|
|
776
|
+
* })
|
|
777
|
+
*
|
|
778
|
+
* @param tableName - The name of the current table
|
|
779
|
+
* @param constraintArg - The third argument (arrow function returning constraints object)
|
|
780
|
+
* @returns Array of relation info
|
|
781
|
+
*/
|
|
782
|
+
function extractRelationsFromForeignKeyConstraints(tableName, constraintArg) {
|
|
783
|
+
const relations = [];
|
|
784
|
+
if (!Node.isArrowFunction(constraintArg)) return relations;
|
|
785
|
+
const body = constraintArg.getBody();
|
|
786
|
+
if (!body) return relations;
|
|
787
|
+
let objExpr = body;
|
|
788
|
+
if (Node.isParenthesizedExpression(objExpr)) objExpr = objExpr.getExpression();
|
|
789
|
+
if (!Node.isObjectLiteralExpression(objExpr)) return relations;
|
|
790
|
+
objExpr.getProperties().forEach((prop) => {
|
|
791
|
+
if (!Node.isPropertyAssignment(prop)) return;
|
|
792
|
+
const initExpr = prop.getInitializer();
|
|
793
|
+
if (!initExpr) return;
|
|
794
|
+
const text = initExpr.getText();
|
|
795
|
+
if (!text.includes("foreignKey(")) return;
|
|
796
|
+
const columnsMatch = text.match(/columns:\s*\[\s*(\w+)\.(\w+)\s*\]/);
|
|
797
|
+
const foreignColumnsMatch = text.match(/foreignColumns:\s*\[\s*(\w+)\.(\w+)\s*\]/);
|
|
798
|
+
if (columnsMatch && foreignColumnsMatch) {
|
|
799
|
+
const toField = columnsMatch[2];
|
|
800
|
+
const fromModel = foreignColumnsMatch[1];
|
|
801
|
+
const fromField = foreignColumnsMatch[2];
|
|
802
|
+
const isRequired = !text.includes(".nullable()");
|
|
803
|
+
relations.push({
|
|
804
|
+
fromModel,
|
|
805
|
+
toModel: tableName,
|
|
806
|
+
fromField,
|
|
807
|
+
toField,
|
|
808
|
+
isRequired
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
return relations;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Extract relations from relations() helper blocks.
|
|
816
|
+
*
|
|
817
|
+
* Pattern:
|
|
818
|
+
* relations(TableRef, ({ one, many }) => ({
|
|
819
|
+
* user: one(User, {
|
|
820
|
+
* fields: [Post.userId],
|
|
821
|
+
* references: [User.id],
|
|
822
|
+
* }),
|
|
823
|
+
* posts: many(Post),
|
|
824
|
+
* }))
|
|
825
|
+
*
|
|
826
|
+
* @param file - The source file
|
|
827
|
+
* @returns Array of relation info
|
|
828
|
+
*/
|
|
829
|
+
function extractRelationsFromRelationBlocks(file) {
|
|
830
|
+
const relations = [];
|
|
831
|
+
file.getVariableStatements().filter((stmt) => stmt.isExported()).forEach((stmt) => {
|
|
832
|
+
const decl = stmt.getDeclarations()[0];
|
|
833
|
+
if (!Node.isVariableDeclaration(decl)) return;
|
|
834
|
+
if (!decl.getName().toLowerCase().includes("relation")) return;
|
|
835
|
+
const init = decl.getInitializer();
|
|
836
|
+
if (!(init && Node.isCallExpression(init))) return;
|
|
837
|
+
if (init.getExpression().getText() !== "relations") return;
|
|
838
|
+
const args = init.getArguments();
|
|
839
|
+
if (args.length < 2) return;
|
|
840
|
+
const tableRef = args[0];
|
|
841
|
+
if (!Node.isIdentifier(tableRef)) return;
|
|
842
|
+
const tableName = tableRef.getText();
|
|
843
|
+
const arrowFn = args[1];
|
|
844
|
+
if (!Node.isArrowFunction(arrowFn)) return;
|
|
845
|
+
const body = arrowFn.getBody();
|
|
846
|
+
if (!body) return;
|
|
847
|
+
let objExpr = body;
|
|
848
|
+
if (Node.isParenthesizedExpression(objExpr)) objExpr = objExpr.getExpression();
|
|
849
|
+
if (!Node.isObjectLiteralExpression(objExpr)) return;
|
|
850
|
+
objExpr.getProperties().forEach((prop) => {
|
|
851
|
+
if (!Node.isPropertyAssignment(prop)) return;
|
|
852
|
+
const initExpr = prop.getInitializer();
|
|
853
|
+
if (!(initExpr && Node.isCallExpression(initExpr))) return;
|
|
854
|
+
if (initExpr.getExpression().getText() !== "one") return;
|
|
855
|
+
const relArgs = initExpr.getArguments();
|
|
856
|
+
if (relArgs.length < 2) return;
|
|
857
|
+
const refTableArg = relArgs[0];
|
|
858
|
+
if (!Node.isIdentifier(refTableArg)) return;
|
|
859
|
+
const refTable = refTableArg.getText();
|
|
860
|
+
const configArg = relArgs[1];
|
|
861
|
+
if (!Node.isObjectLiteralExpression(configArg)) return;
|
|
862
|
+
const configText = configArg.getText();
|
|
863
|
+
const fieldsMatch = configText.match(/fields:\s*\[\s*(\w+)\.(\w+)\s*\]/);
|
|
864
|
+
const referencesMatch = configText.match(/references:\s*\[\s*(\w+)\.(\w+)\s*\]/);
|
|
865
|
+
if (fieldsMatch && referencesMatch) {
|
|
866
|
+
const currentField = fieldsMatch[2];
|
|
867
|
+
const refField = referencesMatch[2];
|
|
868
|
+
const currentFieldLower = currentField.toLowerCase();
|
|
869
|
+
const refFieldLower = refField.toLowerCase();
|
|
870
|
+
const isCurrentFieldFk = currentFieldLower.endsWith("id") && currentFieldLower !== "id" || currentFieldLower.endsWith("_id");
|
|
871
|
+
const isRefFieldFk = refFieldLower.endsWith("id") && refFieldLower !== "id" || refFieldLower.endsWith("_id");
|
|
872
|
+
if (isCurrentFieldFk && !isRefFieldFk) relations.push({
|
|
873
|
+
fromModel: refTable,
|
|
874
|
+
toModel: tableName,
|
|
875
|
+
fromField: refField,
|
|
876
|
+
toField: currentField,
|
|
877
|
+
isRequired: true
|
|
878
|
+
});
|
|
879
|
+
else if (!isCurrentFieldFk && isRefFieldFk) relations.push({
|
|
880
|
+
fromModel: tableName,
|
|
881
|
+
toModel: refTable,
|
|
882
|
+
fromField: currentField,
|
|
883
|
+
toField: refField,
|
|
884
|
+
isRequired: true
|
|
885
|
+
});
|
|
886
|
+
else relations.push({
|
|
887
|
+
fromModel: refTable,
|
|
888
|
+
toModel: tableName,
|
|
889
|
+
fromField: refField,
|
|
890
|
+
toField: currentField,
|
|
891
|
+
isRequired: true
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
});
|
|
896
|
+
return relations;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Parse table information from Drizzle schema code.
|
|
900
|
+
*
|
|
901
|
+
* @param code - Array of source code lines
|
|
902
|
+
* @returns Array of table information
|
|
903
|
+
*/
|
|
904
|
+
function parseTableInfo(code) {
|
|
905
|
+
const source = code.join("\n");
|
|
906
|
+
return new Project({ useInMemoryFileSystem: true }).createSourceFile("temp.ts", source).getVariableStatements().filter((stmt) => stmt.isExported()).flatMap((stmt) => {
|
|
907
|
+
const decl = stmt.getDeclarations()[0];
|
|
908
|
+
if (!Node.isVariableDeclaration(decl)) return [];
|
|
909
|
+
const varName = decl.getName();
|
|
910
|
+
if (varName.toLowerCase().includes("relation")) return [];
|
|
911
|
+
const init = decl.getInitializer();
|
|
912
|
+
if (!(init && Node.isCallExpression(init))) return [];
|
|
913
|
+
const callee = init.getExpression().getText();
|
|
914
|
+
if (!callee.endsWith("Table") || callee === "relations") return [];
|
|
915
|
+
const objLit = init.getArguments()[1];
|
|
916
|
+
if (!(objLit && Node.isObjectLiteralExpression(objLit))) return [];
|
|
917
|
+
return [{
|
|
918
|
+
name: varName,
|
|
919
|
+
fields: objLit.getProperties().filter(Node.isPropertyAssignment).map((prop) => extractFieldInfo(prop, code)).filter(isFieldInfo)
|
|
920
|
+
}];
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Extract relations from Drizzle schema code by analyzing:
|
|
925
|
+
* 1. .references() calls on fields
|
|
926
|
+
* 2. foreignKey() constraints in table definition
|
|
927
|
+
* 3. relations() helper blocks
|
|
928
|
+
*
|
|
929
|
+
* @param code - Array of source code lines
|
|
930
|
+
* @returns Array of relation information (deduplicated)
|
|
931
|
+
*/
|
|
932
|
+
function extractRelationsFromSchema(code) {
|
|
933
|
+
const source = code.join("\n");
|
|
934
|
+
const file = new Project({ useInMemoryFileSystem: true }).createSourceFile("temp.ts", source);
|
|
935
|
+
const relations = [];
|
|
936
|
+
file.getVariableStatements().filter((stmt) => stmt.isExported()).forEach((stmt) => {
|
|
937
|
+
const decl = stmt.getDeclarations()[0];
|
|
938
|
+
if (!Node.isVariableDeclaration(decl)) return;
|
|
939
|
+
const varName = decl.getName();
|
|
940
|
+
if (varName.toLowerCase().includes("relation")) return;
|
|
941
|
+
const init = decl.getInitializer();
|
|
942
|
+
if (!(init && Node.isCallExpression(init))) return;
|
|
943
|
+
const callee = init.getExpression().getText();
|
|
944
|
+
if (!callee.endsWith("Table") || callee === "relations") return;
|
|
945
|
+
const args = init.getArguments();
|
|
946
|
+
const objLit = args[1];
|
|
947
|
+
if (objLit && Node.isObjectLiteralExpression(objLit)) objLit.getProperties().filter(Node.isPropertyAssignment).forEach((prop) => {
|
|
948
|
+
const relation = extractRelationFromField(prop, varName);
|
|
949
|
+
if (relation) relations.push(relation);
|
|
950
|
+
});
|
|
951
|
+
const constraintArg = args[2];
|
|
952
|
+
if (constraintArg && Node.isExpression(constraintArg)) {
|
|
953
|
+
const fkRelations = extractRelationsFromForeignKeyConstraints(varName, constraintArg);
|
|
954
|
+
relations.push(...fkRelations);
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
const relationBlockRelations = extractRelationsFromRelationBlocks(file);
|
|
958
|
+
relations.push(...relationBlockRelations);
|
|
959
|
+
const seen = /* @__PURE__ */ new Set();
|
|
960
|
+
return relations.filter((r) => {
|
|
961
|
+
const key = relationKey(r);
|
|
962
|
+
if (seen.has(key)) return false;
|
|
963
|
+
seen.add(key);
|
|
964
|
+
return true;
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
//#endregion
|
|
969
|
+
//#region src/generator/dbml/generator/dbml-content.ts
|
|
970
|
+
/**
|
|
971
|
+
* DBML content generator for Drizzle schemas
|
|
972
|
+
*
|
|
973
|
+
* Generates DBML (Database Markup Language) format from parsed table and relation info.
|
|
974
|
+
* Uses utils-lab for common DBML generation functions.
|
|
975
|
+
*/
|
|
976
|
+
/**
|
|
977
|
+
* Convert internal FieldInfo to DBMLColumn
|
|
978
|
+
*/
|
|
979
|
+
function toDBMLColumn(field) {
|
|
980
|
+
return {
|
|
981
|
+
name: field.name,
|
|
982
|
+
type: mapDrizzleType(field.type),
|
|
983
|
+
isPrimaryKey: field.keyType === "PK",
|
|
984
|
+
isIncrement: field.type.includes("serial"),
|
|
985
|
+
note: field.description ?? void 0
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Convert internal TableInfo to DBMLTable
|
|
990
|
+
*/
|
|
991
|
+
function toDBMLTable(table) {
|
|
992
|
+
return {
|
|
993
|
+
name: table.name,
|
|
994
|
+
columns: table.fields.map(toDBMLColumn)
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Convert internal RelationInfo to DBMLRef
|
|
999
|
+
*/
|
|
1000
|
+
function toDBMLRef(relation) {
|
|
1001
|
+
return {
|
|
1002
|
+
fromTable: relation.toModel,
|
|
1003
|
+
fromColumn: relation.toField,
|
|
1004
|
+
toTable: relation.fromModel,
|
|
1005
|
+
toColumn: relation.fromField
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Generate complete DBML content
|
|
1010
|
+
*
|
|
1011
|
+
* @param relations - The relations extracted from the schema
|
|
1012
|
+
* @param tables - The tables to generate DBML from
|
|
1013
|
+
* @returns The generated DBML content
|
|
1014
|
+
*/
|
|
1015
|
+
function dbmlContent(relations, tables) {
|
|
1016
|
+
return generateDBMLContent({
|
|
1017
|
+
tables: tables.map(toDBMLTable),
|
|
1018
|
+
refs: relations.map(toDBMLRef)
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
//#endregion
|
|
1023
|
+
//#region src/generator/dbml/index.ts
|
|
1024
|
+
/**
|
|
1025
|
+
* Generate DBML schema from Drizzle schema code
|
|
1026
|
+
*
|
|
1027
|
+
* @param code - The code to generate DBML from
|
|
1028
|
+
* @param output - The output file path
|
|
1029
|
+
*/
|
|
1030
|
+
async function sizukuDBML(code, output) {
|
|
1031
|
+
const tables = parseTableInfo(code);
|
|
1032
|
+
const content = dbmlContent(extractRelationsFromSchema(code), tables);
|
|
1033
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
1034
|
+
if (!mkdirResult.ok) return {
|
|
1035
|
+
ok: false,
|
|
1036
|
+
error: mkdirResult.error
|
|
1037
|
+
};
|
|
1038
|
+
const writeFileResult = await writeFile(output, content);
|
|
1039
|
+
if (!writeFileResult.ok) return {
|
|
1040
|
+
ok: false,
|
|
1041
|
+
error: writeFileResult.error
|
|
1042
|
+
};
|
|
1043
|
+
return {
|
|
1044
|
+
ok: true,
|
|
1045
|
+
value: void 0
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
//#endregion
|
|
1050
|
+
//#region src/generator/effect/generator/effect.ts
|
|
1051
|
+
/**
|
|
1052
|
+
* Generates an Effect schema for a given schema and config.
|
|
1053
|
+
*
|
|
1054
|
+
* @param schema - The schema to generate code for.
|
|
1055
|
+
* @param comment - Whether to include comments in the generated code.
|
|
1056
|
+
* @returns The generated Effect schema.
|
|
1057
|
+
*/
|
|
1058
|
+
function effect(schema, comment) {
|
|
1059
|
+
const inner = fieldDefinitions(schema, comment);
|
|
1060
|
+
return `export const ${makeCapitalized(schema.name)}Schema = Schema.Struct({${inner}})`;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
//#endregion
|
|
1064
|
+
//#region src/generator/effect/generator/effect-code.ts
|
|
1065
|
+
/**
|
|
1066
|
+
* Generates Effect code for a given schema and config.
|
|
1067
|
+
*
|
|
1068
|
+
* @param schema - The schema to generate code for.
|
|
1069
|
+
* @param comment - Whether to include comments in the generated code.
|
|
1070
|
+
* @param type - Whether to include type information in the generated code.
|
|
1071
|
+
* @returns The generated Effect code.
|
|
1072
|
+
*/
|
|
1073
|
+
function effectCode(schema, comment, type) {
|
|
1074
|
+
const effectSchema = effect(schema, comment);
|
|
1075
|
+
if (type) return `${effectSchema}\n\n${inferEffect(schema.name)}\n`;
|
|
1076
|
+
return `${effectSchema}\n`;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
//#endregion
|
|
1080
|
+
//#region src/generator/effect/index.ts
|
|
1081
|
+
/**
|
|
1082
|
+
* Generate Effect schema
|
|
1083
|
+
* @param code - The code to generate Effect schema from
|
|
1084
|
+
* @param output - The output file path
|
|
1085
|
+
* @param comment - Whether to include comments in the generated code
|
|
1086
|
+
* @param type - Whether to include type information in the generated code
|
|
1087
|
+
*/
|
|
1088
|
+
async function sizukuEffect(code, output, comment, type) {
|
|
1089
|
+
const effectGeneratedCode = [
|
|
1090
|
+
`import { Schema } from 'effect'`,
|
|
1091
|
+
"",
|
|
1092
|
+
...extractSchemas(code, "effect").map((schema) => effectCode(schema, comment ?? false, type ?? false))
|
|
1093
|
+
].join("\n");
|
|
1094
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
1095
|
+
if (!mkdirResult.ok) return {
|
|
1096
|
+
ok: false,
|
|
1097
|
+
error: mkdirResult.error
|
|
1098
|
+
};
|
|
1099
|
+
const fmtResult = await fmt(effectGeneratedCode);
|
|
1100
|
+
if (!fmtResult.ok) return {
|
|
1101
|
+
ok: false,
|
|
1102
|
+
error: fmtResult.error
|
|
1103
|
+
};
|
|
1104
|
+
const writeFileResult = await writeFile(output, fmtResult.value);
|
|
1105
|
+
if (!writeFileResult.ok) return {
|
|
1106
|
+
ok: false,
|
|
1107
|
+
error: writeFileResult.error
|
|
1108
|
+
};
|
|
1109
|
+
return {
|
|
1110
|
+
ok: true,
|
|
1111
|
+
value: void 0
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
//#endregion
|
|
1116
|
+
//#region src/generator/mermaid-er/generator/er-content.ts
|
|
1117
|
+
const ER_HEADER = ["```mermaid", "erDiagram"];
|
|
1118
|
+
const ER_FOOTER = ["```"];
|
|
1119
|
+
const RELATIONSHIPS = {
|
|
1120
|
+
"zero-one": "|o",
|
|
1121
|
+
one: "||",
|
|
1122
|
+
"zero-many": "}o",
|
|
1123
|
+
many: "}|"
|
|
1124
|
+
};
|
|
1125
|
+
/**
|
|
1126
|
+
* Generate relation line from relation info.
|
|
1127
|
+
*
|
|
1128
|
+
* @param relation - The relation info
|
|
1129
|
+
* @returns The Mermaid ER relation line
|
|
1130
|
+
*/
|
|
1131
|
+
function generateRelationLine(relation) {
|
|
1132
|
+
const fromSymbol = RELATIONSHIPS.one;
|
|
1133
|
+
const toSymbol = relation.isRequired ? RELATIONSHIPS.many : RELATIONSHIPS["zero-many"];
|
|
1134
|
+
return ` ${relation.fromModel} ${fromSymbol}--${toSymbol} ${relation.toModel} : "(${relation.fromField}) - (${relation.toField})"`;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Remove duplicate relations.
|
|
1138
|
+
*
|
|
1139
|
+
* @param relations - The relations to deduplicate
|
|
1140
|
+
* @returns The deduplicated relations
|
|
1141
|
+
*/
|
|
1142
|
+
function removeDuplicateRelations(relations) {
|
|
1143
|
+
return [...new Set(relations)];
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Generate ER content
|
|
1147
|
+
* @param relations - The relations extracted from .references() calls
|
|
1148
|
+
* @param tables - The tables to generate the ER content from
|
|
1149
|
+
* @returns The generated ER content
|
|
1150
|
+
*/
|
|
1151
|
+
function erContent(relations, tables) {
|
|
1152
|
+
const relationLines = removeDuplicateRelations(relations.map(generateRelationLine));
|
|
1153
|
+
const tableDefinitions = tables.flatMap((table) => [
|
|
1154
|
+
` ${table.name} {`,
|
|
1155
|
+
...table.fields.map((field) => {
|
|
1156
|
+
const keyPart = field.keyType ? ` ${field.keyType}` : "";
|
|
1157
|
+
const descPart = field.description ? ` "${field.description}"` : "";
|
|
1158
|
+
return ` ${field.type} ${field.name}${keyPart}${descPart}`;
|
|
1159
|
+
}),
|
|
1160
|
+
" }"
|
|
1161
|
+
]);
|
|
1162
|
+
return [
|
|
1163
|
+
...ER_HEADER,
|
|
1164
|
+
...relationLines,
|
|
1165
|
+
...tableDefinitions,
|
|
1166
|
+
...ER_FOOTER
|
|
1167
|
+
].join("\n");
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
//#endregion
|
|
1171
|
+
//#region src/generator/mermaid-er/index.ts
|
|
1172
|
+
/**
|
|
1173
|
+
* Generate Mermaid ER diagram
|
|
1174
|
+
* @param code - The code to generate Mermaid ER diagram from
|
|
1175
|
+
* @param output - The output file path
|
|
1176
|
+
*/
|
|
1177
|
+
async function sizukuMermaidER(code, output) {
|
|
1178
|
+
const tables = parseTableInfo(code);
|
|
1179
|
+
const ERContent = erContent(extractRelationsFromSchema(code), tables);
|
|
1180
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
1181
|
+
if (!mkdirResult.ok) return {
|
|
1182
|
+
ok: false,
|
|
1183
|
+
error: mkdirResult.error
|
|
1184
|
+
};
|
|
1185
|
+
const writeFileResult = await writeFile(output, ERContent);
|
|
1186
|
+
if (!writeFileResult.ok) return {
|
|
1187
|
+
ok: false,
|
|
1188
|
+
error: writeFileResult.error
|
|
1189
|
+
};
|
|
1190
|
+
return {
|
|
1191
|
+
ok: true,
|
|
1192
|
+
value: void 0
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
//#endregion
|
|
1197
|
+
//#region src/generator/svg/index.ts
|
|
1198
|
+
/**
|
|
1199
|
+
* Generate diagram from Drizzle schema code
|
|
1200
|
+
*
|
|
1201
|
+
* @param code - The code to generate diagram from
|
|
1202
|
+
* @param output - The output file path
|
|
1203
|
+
* @param format - Output format ('svg', 'png', or 'dot')
|
|
1204
|
+
*/
|
|
1205
|
+
async function sizukuSVG(code, output, format = "png") {
|
|
1206
|
+
const tables = parseTableInfo(code);
|
|
1207
|
+
const dbml = dbmlContent(extractRelationsFromSchema(code), tables);
|
|
1208
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
1209
|
+
if (!mkdirResult.ok) return {
|
|
1210
|
+
ok: false,
|
|
1211
|
+
error: mkdirResult.error
|
|
1212
|
+
};
|
|
1213
|
+
if (format === "dot") {
|
|
1214
|
+
const writeResult = await writeFile(output, run(dbml, "dot"));
|
|
1215
|
+
if (!writeResult.ok) return {
|
|
1216
|
+
ok: false,
|
|
1217
|
+
error: writeResult.error
|
|
1218
|
+
};
|
|
1219
|
+
} else {
|
|
1220
|
+
const svg = run(dbml, "svg");
|
|
1221
|
+
if (format === "png") {
|
|
1222
|
+
const writeResult = await writeFileBinary(output, new Resvg(svg, { font: { loadSystemFonts: true } }).render().asPng());
|
|
1223
|
+
if (!writeResult.ok) return {
|
|
1224
|
+
ok: false,
|
|
1225
|
+
error: writeResult.error
|
|
1226
|
+
};
|
|
1227
|
+
} else {
|
|
1228
|
+
const writeResult = await writeFile(output, svg);
|
|
1229
|
+
if (!writeResult.ok) return {
|
|
1230
|
+
ok: false,
|
|
1231
|
+
error: writeResult.error
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
ok: true,
|
|
1237
|
+
value: void 0
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
//#endregion
|
|
1242
|
+
//#region src/generator/valibot/generator/relation-valibot-code.ts
|
|
1243
|
+
/**
|
|
1244
|
+
* Generates Valibot relation schema code from a relation schema AST extraction.
|
|
1245
|
+
*
|
|
1246
|
+
* @param schema - The relation schema to generate code for.
|
|
1247
|
+
* @param withType - Whether to include type definition.
|
|
1248
|
+
* @returns The generated Valibot relation schema code.
|
|
1249
|
+
*/
|
|
1250
|
+
function relationValibotCode(schema, withType) {
|
|
1251
|
+
const base = schema.baseName;
|
|
1252
|
+
const relName = `${schema.name}Schema`;
|
|
1253
|
+
const baseSchema = `${makeCapitalized$1(base)}Schema`;
|
|
1254
|
+
const fields = schema.fields.map((f) => `${f.name}:${f.definition}`).join(",");
|
|
1255
|
+
const objectType = schema.objectType === "strict" ? "strictObject" : schema.objectType === "loose" ? "looseObject" : "object";
|
|
1256
|
+
const obj = `\nexport const ${makeCapitalized$1(relName)} = v.${objectType}({...${baseSchema}.entries,${fields}})`;
|
|
1257
|
+
if (withType) return `${obj}\n\n${inferInput(schema.name)}\n`;
|
|
1258
|
+
return `${obj}`;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
//#endregion
|
|
1262
|
+
//#region src/generator/valibot/generator/valibot.ts
|
|
1263
|
+
/**
|
|
1264
|
+
* Generates a Valibot schema for a given schema and config.
|
|
1265
|
+
*
|
|
1266
|
+
* @param schema - The schema to generate code for.
|
|
1267
|
+
* @param comment - Whether to include comments in the generated code.
|
|
1268
|
+
* @returns The generated Valibot schema.
|
|
1269
|
+
*/
|
|
1270
|
+
function valibot(schema, comment) {
|
|
1271
|
+
const wrapperType = schema.objectType === "strict" ? "strictObject" : schema.objectType === "loose" ? "looseObject" : "object";
|
|
1272
|
+
const objectCode = makeValibotObject(fieldDefinitions(schema, comment), wrapperType);
|
|
1273
|
+
return `export const ${makeCapitalized(schema.name)}Schema = ${objectCode}`;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
//#endregion
|
|
1277
|
+
//#region src/generator/valibot/generator/valibot-code.ts
|
|
1278
|
+
/**
|
|
1279
|
+
* Generates Valibot code for a given schema and config.
|
|
1280
|
+
*
|
|
1281
|
+
* @param schema - The schema to generate code for.
|
|
1282
|
+
* @param comment - Whether to include comments in the generated code.
|
|
1283
|
+
* @param type - Whether to include type information in the generated code.
|
|
1284
|
+
* @returns The generated Valibot code.
|
|
1285
|
+
*/
|
|
1286
|
+
function valibotCode(schema, comment, type) {
|
|
1287
|
+
const valibotSchema = valibot(schema, comment);
|
|
1288
|
+
if (type) return `${valibotSchema}\n\n${inferInput(schema.name)}\n`;
|
|
1289
|
+
return `${valibotSchema}\n`;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
//#endregion
|
|
1293
|
+
//#region src/generator/valibot/index.ts
|
|
1294
|
+
/**
|
|
1295
|
+
* Generate Valibot schema
|
|
1296
|
+
* @param code - The code to generate Valibot schema from
|
|
1297
|
+
* @param output - The output file path
|
|
1298
|
+
* @param comment - Whether to include comments in the generated code
|
|
1299
|
+
* @param type - Whether to include type information in the generated code
|
|
1300
|
+
*/
|
|
1301
|
+
async function sizukuValibot(code, output, comment, type, relation) {
|
|
1302
|
+
const baseSchemas = extractSchemas(code, "valibot");
|
|
1303
|
+
const relationSchemas = extractRelationSchemas(code, "valibot");
|
|
1304
|
+
const valibotGeneratedCode = [
|
|
1305
|
+
"import * as v from 'valibot'",
|
|
1306
|
+
"",
|
|
1307
|
+
...baseSchemas.map((schema) => valibotCode(schema, comment ?? false, type ?? false)),
|
|
1308
|
+
...relation ? relationSchemas.map((schema) => relationValibotCode(schema, type ?? false)) : []
|
|
1309
|
+
].join("\n");
|
|
1310
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
1311
|
+
if (!mkdirResult.ok) return {
|
|
1312
|
+
ok: false,
|
|
1313
|
+
error: mkdirResult.error
|
|
1314
|
+
};
|
|
1315
|
+
const fmtResult = await fmt(valibotGeneratedCode);
|
|
1316
|
+
if (!fmtResult.ok) return {
|
|
1317
|
+
ok: false,
|
|
1318
|
+
error: fmtResult.error
|
|
1319
|
+
};
|
|
1320
|
+
const writeFileResult = await writeFile(output, fmtResult.value);
|
|
1321
|
+
if (!writeFileResult.ok) return {
|
|
1322
|
+
ok: false,
|
|
1323
|
+
error: writeFileResult.error
|
|
1324
|
+
};
|
|
1325
|
+
return {
|
|
1326
|
+
ok: true,
|
|
1327
|
+
value: void 0
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
//#endregion
|
|
1332
|
+
//#region src/generator/zod/generator/relation-zod-code.ts
|
|
1333
|
+
/**
|
|
1334
|
+
* Generates Zod relation schema code from a relation schema AST extraction.
|
|
1335
|
+
*
|
|
1336
|
+
* @param schema - The relation schema to generate code for.
|
|
1337
|
+
* @param withType - Whether to include type definition.
|
|
1338
|
+
* @returns The generated Zod relation schema code.
|
|
1339
|
+
*/
|
|
1340
|
+
function relationZodCode(schema, withType) {
|
|
1341
|
+
const base = schema.baseName;
|
|
1342
|
+
const relName = `${schema.name}Schema`;
|
|
1343
|
+
const baseSchema = `${makeCapitalized$1(base)}Schema`;
|
|
1344
|
+
const fields = schema.fields.map((f) => `${f.name}:${f.definition}`).join(",");
|
|
1345
|
+
const objectType = schema.objectType === "strict" ? "strictObject" : schema.objectType === "loose" ? "looseObject" : "object";
|
|
1346
|
+
const obj = `\nexport const ${makeCapitalized$1(relName)} = z.${objectType}({...${baseSchema}.shape,${fields}})`;
|
|
1347
|
+
if (withType) return `${obj}\n\n${infer(schema.name)}\n`;
|
|
1348
|
+
return `${obj}`;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
//#endregion
|
|
1352
|
+
//#region src/generator/zod/generator/zod.ts
|
|
1353
|
+
/**
|
|
1354
|
+
* Generates a Zod schema for a given schema and config.
|
|
1355
|
+
*
|
|
1356
|
+
* @param schema - The schema to generate code for.
|
|
1357
|
+
* @param comment - Whether to include comments in the generated code.
|
|
1358
|
+
* @returns The generated Zod schema.
|
|
1359
|
+
*/
|
|
1360
|
+
function zod(schema, comment) {
|
|
1361
|
+
const wrapperType = schema.objectType === "strict" ? "strictObject" : schema.objectType === "loose" ? "looseObject" : "object";
|
|
1362
|
+
const objectCode = makeZodObject(fieldDefinitions(schema, comment), wrapperType);
|
|
1363
|
+
return `export const ${makeCapitalized(schema.name)}Schema = ${objectCode}`;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
//#endregion
|
|
1367
|
+
//#region src/generator/zod/generator/zod-code.ts
|
|
1368
|
+
/**
|
|
1369
|
+
* Generates Zod code for a given schema and config.
|
|
1370
|
+
*
|
|
1371
|
+
* @param schema - The schema to generate code for.
|
|
1372
|
+
* @param comment - Whether to include comments in the generated code.
|
|
1373
|
+
* @param type - Whether to include type information in the generated code.
|
|
1374
|
+
* @returns The generated Zod code.
|
|
1375
|
+
*/
|
|
1376
|
+
function zodCode(schema, comment, type) {
|
|
1377
|
+
const zodSchema = zod(schema, comment);
|
|
1378
|
+
if (type) return `${zodSchema}\n\n${infer(schema.name)}\n`;
|
|
1379
|
+
return `${zodSchema}\n`;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
//#endregion
|
|
1383
|
+
//#region src/generator/zod/index.ts
|
|
1384
|
+
/**
|
|
1385
|
+
* Generate Zod schema
|
|
1386
|
+
* @param code - The code to generate Zod schema from
|
|
1387
|
+
* @param output - The output file path
|
|
1388
|
+
* @param comment - Whether to include comments in the generated code
|
|
1389
|
+
* @param type - Whether to include type information in the generated code
|
|
1390
|
+
* @param zod - The Zod version to use
|
|
1391
|
+
*/
|
|
1392
|
+
async function sizukuZod(code, output, comment, type, zod, relation) {
|
|
1393
|
+
const importLine = zod === "mini" ? `import * as z from 'zod/mini'` : zod === "@hono/zod-openapi" ? `import { z } from '@hono/zod-openapi'` : `import * as z from 'zod'`;
|
|
1394
|
+
const baseSchemas = extractSchemas(code, "zod");
|
|
1395
|
+
const relationSchemas = extractRelationSchemas(code, "zod");
|
|
1396
|
+
const zodGeneratedCode = [
|
|
1397
|
+
importLine,
|
|
1398
|
+
"",
|
|
1399
|
+
...baseSchemas.map((schema) => zodCode(schema, comment ?? false, type ?? false)),
|
|
1400
|
+
...relation ? relationSchemas.map((schema) => relationZodCode(schema, type ?? false)) : []
|
|
1401
|
+
].join("\n");
|
|
1402
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
1403
|
+
if (!mkdirResult.ok) return {
|
|
1404
|
+
ok: false,
|
|
1405
|
+
error: mkdirResult.error
|
|
1406
|
+
};
|
|
1407
|
+
const fmtResult = await fmt(zodGeneratedCode);
|
|
1408
|
+
if (!fmtResult.ok) return {
|
|
1409
|
+
ok: false,
|
|
1410
|
+
error: fmtResult.error
|
|
1411
|
+
};
|
|
1412
|
+
const writeFileResult = await writeFile(output, fmtResult.value);
|
|
1413
|
+
if (!writeFileResult.ok) return {
|
|
1414
|
+
ok: false,
|
|
1415
|
+
error: writeFileResult.error
|
|
1416
|
+
};
|
|
1417
|
+
return {
|
|
1418
|
+
ok: true,
|
|
1419
|
+
value: void 0
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
//#endregion
|
|
1424
|
+
//#region src/shared/fs/index.ts
|
|
1425
|
+
function readFileSync(path) {
|
|
1426
|
+
try {
|
|
1427
|
+
return {
|
|
1428
|
+
ok: true,
|
|
1429
|
+
value: fs.readFileSync(path, "utf-8")
|
|
1430
|
+
};
|
|
1431
|
+
} catch (e) {
|
|
1432
|
+
return {
|
|
1433
|
+
ok: false,
|
|
1434
|
+
error: e instanceof Error ? e.message : String(e)
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
//#endregion
|
|
1440
|
+
//#region src/index.ts
|
|
1441
|
+
async function main() {
|
|
1442
|
+
const configResult = await config();
|
|
1443
|
+
if (!configResult.ok) return {
|
|
1444
|
+
ok: false,
|
|
1445
|
+
error: configResult.error
|
|
1446
|
+
};
|
|
1447
|
+
const c = configResult.value;
|
|
1448
|
+
const contentResult = readFileSync(c.input);
|
|
1449
|
+
if (!contentResult.ok) return {
|
|
1450
|
+
ok: false,
|
|
1451
|
+
error: contentResult.error
|
|
1452
|
+
};
|
|
1453
|
+
const lines = contentResult.value.split("\n");
|
|
1454
|
+
const codeStart = lines.findIndex((line) => !line.trim().startsWith("import") && line.trim() !== "");
|
|
1455
|
+
const code = lines.slice(codeStart);
|
|
1456
|
+
const zodMessage = c.zod?.output ? await (async () => {
|
|
1457
|
+
const zodConfig = c.zod;
|
|
1458
|
+
if (!zodConfig?.output) return {
|
|
1459
|
+
ok: false,
|
|
1460
|
+
error: "Zod config is missing"
|
|
1461
|
+
};
|
|
1462
|
+
const zodResult = await sizukuZod(code, zodConfig.output, zodConfig.comment, zodConfig.type, zodConfig.zod, zodConfig.relation);
|
|
1463
|
+
if (!zodResult.ok) return {
|
|
1464
|
+
ok: false,
|
|
1465
|
+
error: zodResult.error
|
|
1466
|
+
};
|
|
1467
|
+
return {
|
|
1468
|
+
ok: true,
|
|
1469
|
+
value: `Generated Zod schema at: ${zodConfig.output}`
|
|
1470
|
+
};
|
|
1471
|
+
})() : null;
|
|
1472
|
+
if (zodMessage && !zodMessage.ok) return {
|
|
1473
|
+
ok: false,
|
|
1474
|
+
error: zodMessage.error
|
|
1475
|
+
};
|
|
1476
|
+
const valibotMessage = c.valibot?.output ? await (async () => {
|
|
1477
|
+
const valibotConfig = c.valibot;
|
|
1478
|
+
if (!valibotConfig?.output) return {
|
|
1479
|
+
ok: false,
|
|
1480
|
+
error: "Valibot config is missing"
|
|
1481
|
+
};
|
|
1482
|
+
const valibotResult = await sizukuValibot(code, valibotConfig.output, valibotConfig.comment, valibotConfig.type, valibotConfig.relation);
|
|
1483
|
+
if (!valibotResult.ok) return {
|
|
1484
|
+
ok: false,
|
|
1485
|
+
error: valibotResult.error
|
|
1486
|
+
};
|
|
1487
|
+
return {
|
|
1488
|
+
ok: true,
|
|
1489
|
+
value: `Generated Valibot schema at: ${valibotConfig.output}`
|
|
1490
|
+
};
|
|
1491
|
+
})() : null;
|
|
1492
|
+
if (valibotMessage && !valibotMessage.ok) return {
|
|
1493
|
+
ok: false,
|
|
1494
|
+
error: valibotMessage.error
|
|
1495
|
+
};
|
|
1496
|
+
const arktypeMessage = c.arktype?.output ? await (async () => {
|
|
1497
|
+
const arktypeConfig = c.arktype;
|
|
1498
|
+
if (!arktypeConfig?.output) return {
|
|
1499
|
+
ok: false,
|
|
1500
|
+
error: "ArkType config is missing"
|
|
1501
|
+
};
|
|
1502
|
+
const arktypeResult = await sizukuArktype(code, arktypeConfig.output, arktypeConfig.comment, arktypeConfig.type);
|
|
1503
|
+
if (!arktypeResult.ok) return {
|
|
1504
|
+
ok: false,
|
|
1505
|
+
error: arktypeResult.error
|
|
1506
|
+
};
|
|
1507
|
+
return {
|
|
1508
|
+
ok: true,
|
|
1509
|
+
value: `Generated ArkType schema at: ${arktypeConfig.output}`
|
|
1510
|
+
};
|
|
1511
|
+
})() : null;
|
|
1512
|
+
if (arktypeMessage && !arktypeMessage.ok) return {
|
|
1513
|
+
ok: false,
|
|
1514
|
+
error: arktypeMessage.error
|
|
1515
|
+
};
|
|
1516
|
+
const effectMessage = c.effect?.output ? await (async () => {
|
|
1517
|
+
const effectConfig = c.effect;
|
|
1518
|
+
if (!effectConfig?.output) return {
|
|
1519
|
+
ok: false,
|
|
1520
|
+
error: "Effect config is missing"
|
|
1521
|
+
};
|
|
1522
|
+
const effectResult = await sizukuEffect(code, effectConfig.output, effectConfig.comment, effectConfig.type);
|
|
1523
|
+
if (!effectResult.ok) return {
|
|
1524
|
+
ok: false,
|
|
1525
|
+
error: effectResult.error
|
|
1526
|
+
};
|
|
1527
|
+
return {
|
|
1528
|
+
ok: true,
|
|
1529
|
+
value: `Generated Effect schema at: ${effectConfig.output}`
|
|
1530
|
+
};
|
|
1531
|
+
})() : null;
|
|
1532
|
+
if (effectMessage && !effectMessage.ok) return {
|
|
1533
|
+
ok: false,
|
|
1534
|
+
error: effectMessage.error
|
|
1535
|
+
};
|
|
1536
|
+
const mermaidMessage = c.mermaid?.output ? await (async () => {
|
|
1537
|
+
const mermaidConfig = c.mermaid;
|
|
1538
|
+
if (!mermaidConfig?.output) return {
|
|
1539
|
+
ok: false,
|
|
1540
|
+
error: "Mermaid config is missing"
|
|
1541
|
+
};
|
|
1542
|
+
const mermaidResult = await sizukuMermaidER(code, mermaidConfig.output);
|
|
1543
|
+
if (!mermaidResult.ok) return {
|
|
1544
|
+
ok: false,
|
|
1545
|
+
error: mermaidResult.error
|
|
1546
|
+
};
|
|
1547
|
+
return {
|
|
1548
|
+
ok: true,
|
|
1549
|
+
value: `Generated Mermaid ER at: ${mermaidConfig.output}`
|
|
1550
|
+
};
|
|
1551
|
+
})() : null;
|
|
1552
|
+
if (mermaidMessage && !mermaidMessage.ok) return {
|
|
1553
|
+
ok: false,
|
|
1554
|
+
error: mermaidMessage.error
|
|
1555
|
+
};
|
|
1556
|
+
const dbmlMessage = c.dbml?.output ? await (async () => {
|
|
1557
|
+
const dbmlConfig = c.dbml;
|
|
1558
|
+
if (!dbmlConfig?.output) return {
|
|
1559
|
+
ok: false,
|
|
1560
|
+
error: "DBML config is missing"
|
|
1561
|
+
};
|
|
1562
|
+
const dbmlResult = await sizukuDBML(code, dbmlConfig.output);
|
|
1563
|
+
if (!dbmlResult.ok) return {
|
|
1564
|
+
ok: false,
|
|
1565
|
+
error: dbmlResult.error
|
|
1566
|
+
};
|
|
1567
|
+
return {
|
|
1568
|
+
ok: true,
|
|
1569
|
+
value: `Generated DBML schema at: ${dbmlConfig.output}`
|
|
1570
|
+
};
|
|
1571
|
+
})() : null;
|
|
1572
|
+
if (dbmlMessage && !dbmlMessage.ok) return {
|
|
1573
|
+
ok: false,
|
|
1574
|
+
error: dbmlMessage.error
|
|
1575
|
+
};
|
|
1576
|
+
const svgMessage = c.svg?.output ? await (async () => {
|
|
1577
|
+
const svgConfig = c.svg;
|
|
1578
|
+
if (!svgConfig?.output) return {
|
|
1579
|
+
ok: false,
|
|
1580
|
+
error: "SVG config is missing"
|
|
1581
|
+
};
|
|
1582
|
+
const svgResult = await sizukuSVG(code, svgConfig.output, svgConfig.format);
|
|
1583
|
+
if (!svgResult.ok) return {
|
|
1584
|
+
ok: false,
|
|
1585
|
+
error: svgResult.error
|
|
1586
|
+
};
|
|
1587
|
+
return {
|
|
1588
|
+
ok: true,
|
|
1589
|
+
value: `Generated SVG diagram at: ${svgConfig.output}`
|
|
1590
|
+
};
|
|
1591
|
+
})() : null;
|
|
1592
|
+
if (svgMessage && !svgMessage.ok) return {
|
|
1593
|
+
ok: false,
|
|
1594
|
+
error: svgMessage.error
|
|
1595
|
+
};
|
|
1596
|
+
return {
|
|
1597
|
+
ok: true,
|
|
1598
|
+
value: [
|
|
1599
|
+
zodMessage?.ok ? zodMessage.value : null,
|
|
1600
|
+
valibotMessage?.ok ? valibotMessage.value : null,
|
|
1601
|
+
arktypeMessage?.ok ? arktypeMessage.value : null,
|
|
1602
|
+
effectMessage?.ok ? effectMessage.value : null,
|
|
1603
|
+
mermaidMessage?.ok ? mermaidMessage.value : null,
|
|
1604
|
+
dbmlMessage?.ok ? dbmlMessage.value : null,
|
|
1605
|
+
svgMessage?.ok ? svgMessage.value : null
|
|
1606
|
+
].filter((msg) => msg !== null).join("\n")
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
main().then((result) => {
|
|
1610
|
+
if (result?.ok) console.log(result.value);
|
|
1611
|
+
else console.error(result?.error);
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1614
|
+
//#endregion
|
|
1615
|
+
export { main };
|