sizuku 0.2.0 → 0.3.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 +27 -15
- package/dist/config/index.d.ts +16 -7
- package/dist/config/index.js +83 -11
- package/dist/generator/mermaid-er/generator/er-content.js +1 -1
- package/dist/generator/mermaid-er/validator/index.js +5 -2
- package/dist/generator/valibot/index.d.ts +1 -1
- package/dist/generator/valibot/index.js +2 -2
- package/dist/generator/zod/index.d.ts +1 -1
- package/dist/generator/zod/index.js +2 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +16 -14
- package/dist/utils/index.js +33 -84
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -72,22 +72,26 @@ export const postRelations = relations(post, ({ one }) => ({
|
|
|
72
72
|
|
|
73
73
|
Prepare sizuku.json:
|
|
74
74
|
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
```ts
|
|
76
|
+
import defineConfig from 'sizuku/config'
|
|
77
|
+
|
|
78
|
+
export default defineConfig({
|
|
79
|
+
input: 'db/schema.ts',
|
|
80
|
+
zod: {
|
|
81
|
+
output: 'zod/index.ts',
|
|
82
|
+
comment: true,
|
|
83
|
+
type: true,
|
|
84
|
+
zod: 'v4',
|
|
85
|
+
},
|
|
86
|
+
valibot: {
|
|
87
|
+
output: 'valibot/index.ts',
|
|
88
|
+
comment: true,
|
|
89
|
+
type: true,
|
|
82
90
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"comment": true
|
|
91
|
+
mermaid: {
|
|
92
|
+
output: 'mermaid-er/ER.md',
|
|
86
93
|
},
|
|
87
|
-
|
|
88
|
-
"output": "mermaid-er/ER.md"
|
|
89
|
-
}
|
|
90
|
-
}
|
|
94
|
+
})
|
|
91
95
|
```
|
|
92
96
|
|
|
93
97
|
Run Sizuku:
|
|
@@ -160,6 +164,8 @@ export const UserSchema = v.object({
|
|
|
160
164
|
name: v.pipe(v.string(), v.minLength(1), v.maxLength(50)),
|
|
161
165
|
})
|
|
162
166
|
|
|
167
|
+
export type User = v.InferInput<typeof UserSchema>
|
|
168
|
+
|
|
163
169
|
export const PostSchema = v.object({
|
|
164
170
|
/**
|
|
165
171
|
* Primary key
|
|
@@ -179,9 +185,15 @@ export const PostSchema = v.object({
|
|
|
179
185
|
userId: v.pipe(v.string(), v.uuid()),
|
|
180
186
|
})
|
|
181
187
|
|
|
188
|
+
export type Post = v.InferInput<typeof PostSchema>
|
|
189
|
+
|
|
182
190
|
export const UserRelationsSchema = v.object({ ...UserSchema.entries, posts: v.array(PostSchema) })
|
|
183
191
|
|
|
192
|
+
export type UserRelations = v.InferInput<typeof UserRelationsSchema>
|
|
193
|
+
|
|
184
194
|
export const PostRelationsSchema = v.object({ ...PostSchema.entries, user: UserSchema })
|
|
195
|
+
|
|
196
|
+
export type PostRelations = v.InferInput<typeof PostRelationsSchema>
|
|
185
197
|
```
|
|
186
198
|
|
|
187
199
|
### Mermaid ER
|
|
@@ -203,7 +215,7 @@ erDiagram
|
|
|
203
215
|
|
|
204
216
|
### ⚠️ WARNING: Potential Breaking Changes Without Notice
|
|
205
217
|
|
|
206
|
-
|
|
218
|
+
This package is in active development and may introduce breaking changes without prior notice.
|
|
207
219
|
|
|
208
220
|
## License
|
|
209
221
|
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
export type Config = {
|
|
2
|
-
input
|
|
1
|
+
export type Config = Readonly<{
|
|
2
|
+
input: `${string}.ts`;
|
|
3
3
|
zod?: {
|
|
4
|
-
output
|
|
4
|
+
output: `${string}.ts`;
|
|
5
5
|
comment?: boolean;
|
|
6
6
|
type?: boolean;
|
|
7
7
|
zod?: 'v4' | 'mini' | '@hono/zod-openapi';
|
|
8
|
+
relation?: boolean;
|
|
8
9
|
};
|
|
9
10
|
valibot?: {
|
|
10
|
-
output
|
|
11
|
+
output: `${string}.ts`;
|
|
11
12
|
comment?: boolean;
|
|
12
13
|
type?: boolean;
|
|
14
|
+
relation?: boolean;
|
|
13
15
|
};
|
|
14
16
|
mermaid?: {
|
|
15
|
-
output
|
|
17
|
+
output: string;
|
|
16
18
|
};
|
|
17
|
-
}
|
|
18
|
-
export declare function
|
|
19
|
+
}>;
|
|
20
|
+
export declare function config(): Promise<{
|
|
21
|
+
ok: true;
|
|
22
|
+
value: Config;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
error: string;
|
|
26
|
+
}>;
|
|
27
|
+
export default function defineConfig(config: Config): Config;
|
package/dist/config/index.js
CHANGED
|
@@ -1,13 +1,85 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { register } from 'tsx/esm/api';
|
|
5
|
+
export async function config() {
|
|
6
|
+
const isTs = (o) => o.endsWith('.ts');
|
|
7
|
+
const abs = resolve(process.cwd(), 'sizuku.config.ts');
|
|
8
|
+
if (!existsSync(abs)) {
|
|
9
|
+
return { ok: false, error: `Config not found: ${abs}` };
|
|
5
10
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
try {
|
|
12
|
+
register();
|
|
13
|
+
const mod = await import(pathToFileURL(abs).href);
|
|
14
|
+
if (!('default' in mod)) {
|
|
15
|
+
return { ok: false, error: 'Config must export default object' };
|
|
16
|
+
}
|
|
17
|
+
if (mod.default !== undefined) {
|
|
18
|
+
if (!isTs(mod.default.input)) {
|
|
19
|
+
return { ok: false, error: 'Input must be a .ts file' };
|
|
20
|
+
}
|
|
21
|
+
// zod
|
|
22
|
+
if (mod.default.zod !== undefined) {
|
|
23
|
+
if (!isTs(mod.default.zod.output)) {
|
|
24
|
+
return { ok: false, error: 'Zod output must be a .ts file' };
|
|
25
|
+
}
|
|
26
|
+
if (mod.default.zod.comment !== undefined) {
|
|
27
|
+
if (typeof mod.default.zod.comment !== 'boolean') {
|
|
28
|
+
return { ok: false, error: 'Zod comment must be a boolean' };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (mod.default.zod.type !== undefined) {
|
|
32
|
+
if (typeof mod.default.zod.type !== 'boolean') {
|
|
33
|
+
return { ok: false, error: 'Zod type must be a boolean' };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (mod.default.zod.zod !== undefined) {
|
|
37
|
+
if (mod.default.zod.zod !== 'v4' &&
|
|
38
|
+
mod.default.zod.zod !== 'mini' &&
|
|
39
|
+
mod.default.zod.zod !== '@hono/zod-openapi') {
|
|
40
|
+
return { ok: false, error: 'zod must be v4, mini, or @hono/zod-openapi' };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (mod.default.zod.relation !== undefined) {
|
|
44
|
+
if (typeof mod.default.zod.relation !== 'boolean') {
|
|
45
|
+
return { ok: false, error: 'Zod relation must be a boolean' };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// valibot
|
|
51
|
+
if (mod.default.valibot !== undefined) {
|
|
52
|
+
if (!isTs(mod.default.valibot.output)) {
|
|
53
|
+
return { ok: false, error: 'Valibot output must be a .ts file' };
|
|
54
|
+
}
|
|
55
|
+
if (mod.default.valibot.comment !== undefined) {
|
|
56
|
+
if (typeof mod.default.valibot.comment !== 'boolean') {
|
|
57
|
+
return { ok: false, error: 'Valibot comment must be a boolean' };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (mod.default.valibot.type !== undefined) {
|
|
61
|
+
if (typeof mod.default.valibot.type !== 'boolean') {
|
|
62
|
+
return { ok: false, error: 'Valibot type must be a boolean' };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (mod.default.valibot.relation !== undefined) {
|
|
66
|
+
if (typeof mod.default.valibot.relation !== 'boolean') {
|
|
67
|
+
return { ok: false, error: 'Valibot relation must be a boolean' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// mermaid
|
|
72
|
+
if (mod.default.mermaid !== undefined) {
|
|
73
|
+
if (typeof mod.default.mermaid.output !== 'string') {
|
|
74
|
+
return { ok: false, error: 'Mermaid output must be a string' };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { ok: true, value: mod.default };
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export default function defineConfig(config) {
|
|
84
|
+
return config;
|
|
13
85
|
}
|
|
@@ -14,7 +14,7 @@ export function erContent(relations, tables) {
|
|
|
14
14
|
// Generate per-table definitions
|
|
15
15
|
...tables.flatMap((table) => [
|
|
16
16
|
` ${table.name} {`,
|
|
17
|
-
...table.fields.map((field) => ` ${field.type} ${field.name}
|
|
17
|
+
...table.fields.map((field) => ` ${field.type} ${field.name}${field.description ? ` "${field.description}"` : ''}`),
|
|
18
18
|
' }',
|
|
19
19
|
]),
|
|
20
20
|
...ER_FOOTER,
|
|
@@ -43,7 +43,8 @@ export function parseTableInfo(code) {
|
|
|
43
43
|
const fieldType = baseBuilderName(initExpr);
|
|
44
44
|
const initText = initExpr.getText();
|
|
45
45
|
const lineIdx = prop.getStartLineNumber() - 1;
|
|
46
|
-
|
|
46
|
+
// Find the immediate comment above this field
|
|
47
|
+
const immediateComment = code
|
|
47
48
|
.slice(0, lineIdx)
|
|
48
49
|
.reverse()
|
|
49
50
|
.find((line) => {
|
|
@@ -59,10 +60,12 @@ export function parseTableInfo(code) {
|
|
|
59
60
|
: initText.includes('.references(')
|
|
60
61
|
? '(FK) '
|
|
61
62
|
: '';
|
|
63
|
+
// Only include description if there's a comment
|
|
64
|
+
const description = immediateComment ? `${prefix}${immediateComment}`.trim() : null;
|
|
62
65
|
return {
|
|
63
66
|
name: fieldName,
|
|
64
67
|
type: fieldType,
|
|
65
|
-
description
|
|
68
|
+
description,
|
|
66
69
|
};
|
|
67
70
|
})
|
|
68
71
|
.filter(isFieldInfo);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @param comment - Whether to include comments in the generated code
|
|
6
6
|
* @param type - Whether to include type information in the generated code
|
|
7
7
|
*/
|
|
8
|
-
export declare function sizukuValibot(code: string[], output: `${string}.ts`, comment?: boolean, type?: boolean,
|
|
8
|
+
export declare function sizukuValibot(code: string[], output: `${string}.ts`, comment?: boolean, type?: boolean, relation?: boolean): Promise<{
|
|
9
9
|
ok: true;
|
|
10
10
|
value: undefined;
|
|
11
11
|
} | {
|
|
@@ -11,14 +11,14 @@ import { valibotCode } from './generator/valibot-code.js';
|
|
|
11
11
|
* @param comment - Whether to include comments in the generated code
|
|
12
12
|
* @param type - Whether to include type information in the generated code
|
|
13
13
|
*/
|
|
14
|
-
export async function sizukuValibot(code, output, comment, type,
|
|
14
|
+
export async function sizukuValibot(code, output, comment, type, relation) {
|
|
15
15
|
const baseSchemas = extractSchemas(code, 'valibot');
|
|
16
16
|
const relationSchemas = extractRelationSchemas(code, 'valibot');
|
|
17
17
|
const valibotGeneratedCode = [
|
|
18
18
|
"import * as v from 'valibot'",
|
|
19
19
|
'',
|
|
20
20
|
...baseSchemas.map((schema) => valibotCode(schema, comment ?? false, type ?? false)),
|
|
21
|
-
...(
|
|
21
|
+
...(relation
|
|
22
22
|
? relationSchemas.map((schema) => relationValibotCode(schema, type ?? false))
|
|
23
23
|
: []),
|
|
24
24
|
].join('\n');
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @param type - Whether to include type information in the generated code
|
|
7
7
|
* @param zod - The Zod version to use
|
|
8
8
|
*/
|
|
9
|
-
export declare function sizukuZod(code: string[], output: `${string}.ts`, comment?: boolean, type?: boolean, zod?: 'v4' | 'mini' | '@hono/zod-openapi',
|
|
9
|
+
export declare function sizukuZod(code: string[], output: `${string}.ts`, comment?: boolean, type?: boolean, zod?: 'v4' | 'mini' | '@hono/zod-openapi', relation?: boolean): Promise<{
|
|
10
10
|
ok: true;
|
|
11
11
|
value: undefined;
|
|
12
12
|
} | {
|
|
@@ -12,7 +12,7 @@ import { zodCode } from './generator/zod-code.js';
|
|
|
12
12
|
* @param type - Whether to include type information in the generated code
|
|
13
13
|
* @param zod - The Zod version to use
|
|
14
14
|
*/
|
|
15
|
-
export async function sizukuZod(code, output, comment, type, zod,
|
|
15
|
+
export async function sizukuZod(code, output, comment, type, zod, relation) {
|
|
16
16
|
const importLine = zod === 'mini'
|
|
17
17
|
? `import * as z from 'zod/mini'`
|
|
18
18
|
: zod === '@hono/zod-openapi'
|
|
@@ -24,7 +24,7 @@ export async function sizukuZod(code, output, comment, type, zod, relations) {
|
|
|
24
24
|
importLine,
|
|
25
25
|
'',
|
|
26
26
|
...baseSchemas.map((schema) => zodCode(schema, comment ?? false, type ?? false)),
|
|
27
|
-
...(
|
|
27
|
+
...(relation ? relationSchemas.map((schema) => relationZodCode(schema, type ?? false)) : []),
|
|
28
28
|
].join('\n');
|
|
29
29
|
const mkdirResult = await mkdir(path.dirname(output));
|
|
30
30
|
if (!mkdirResult.ok) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
// #!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { config } from './config/index.js';
|
|
3
3
|
import { sizukuMermaidER } from './generator/mermaid-er/index.js';
|
|
4
4
|
import { sizukuValibot } from './generator/valibot/index.js';
|
|
5
5
|
import { sizukuZod } from './generator/zod/index.js';
|
|
6
6
|
import { readFileSync } from './shared/fs/index.js';
|
|
7
|
-
export async function main(
|
|
8
|
-
|
|
7
|
+
export async function main() {
|
|
8
|
+
const configResult = await config();
|
|
9
|
+
if (!configResult.ok) {
|
|
9
10
|
return {
|
|
10
11
|
ok: false,
|
|
11
|
-
error:
|
|
12
|
+
error: configResult.error,
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
|
-
const
|
|
15
|
+
const c = configResult.value;
|
|
16
|
+
const contentResult = readFileSync(c.input);
|
|
15
17
|
if (!contentResult.ok) {
|
|
16
18
|
return {
|
|
17
19
|
ok: false,
|
|
@@ -24,37 +26,37 @@ export async function main(config = getConfig()) {
|
|
|
24
26
|
const code = lines.slice(codeStart);
|
|
25
27
|
const results = [];
|
|
26
28
|
/* zod */
|
|
27
|
-
if (
|
|
28
|
-
const zodResult = await sizukuZod(code,
|
|
29
|
+
if (c.zod?.output) {
|
|
30
|
+
const zodResult = await sizukuZod(code, c.zod.output, c.zod.comment, c.zod.type, c.zod.zod, c.zod.relation);
|
|
29
31
|
if (!zodResult.ok) {
|
|
30
32
|
return {
|
|
31
33
|
ok: false,
|
|
32
34
|
error: zodResult.error,
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
|
-
results.push(`Generated Zod schema at: ${
|
|
37
|
+
results.push(`Generated Zod schema at: ${c.zod?.output}`);
|
|
36
38
|
}
|
|
37
39
|
/* valibot */
|
|
38
|
-
if (
|
|
39
|
-
const valibotResult = await sizukuValibot(code,
|
|
40
|
+
if (c.valibot?.output) {
|
|
41
|
+
const valibotResult = await sizukuValibot(code, c.valibot.output, c.valibot.comment, c.valibot.type, c.valibot.relation);
|
|
40
42
|
if (!valibotResult.ok) {
|
|
41
43
|
return {
|
|
42
44
|
ok: false,
|
|
43
45
|
error: valibotResult.error,
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
|
-
results.push(`Generated Valibot schema at: ${
|
|
48
|
+
results.push(`Generated Valibot schema at: ${c.valibot?.output}`);
|
|
47
49
|
}
|
|
48
50
|
/* mermaid */
|
|
49
|
-
if (
|
|
50
|
-
const mermaidResult = await sizukuMermaidER(code,
|
|
51
|
+
if (c.mermaid?.output) {
|
|
52
|
+
const mermaidResult = await sizukuMermaidER(code, c.mermaid.output);
|
|
51
53
|
if (!mermaidResult.ok) {
|
|
52
54
|
return {
|
|
53
55
|
ok: false,
|
|
54
56
|
error: mermaidResult.error,
|
|
55
57
|
};
|
|
56
58
|
}
|
|
57
|
-
results.push(`Generated Mermaid ER at: ${
|
|
59
|
+
results.push(`Generated Mermaid ER at: ${c.mermaid?.output}`);
|
|
58
60
|
}
|
|
59
61
|
return {
|
|
60
62
|
ok: true,
|
package/dist/utils/index.js
CHANGED
|
@@ -73,19 +73,7 @@ export function joinWithSpace(arr) {
|
|
|
73
73
|
* @returns Array of strings split by newline.
|
|
74
74
|
*/
|
|
75
75
|
export function splitByNewline(str) {
|
|
76
|
-
|
|
77
|
-
let current = '';
|
|
78
|
-
for (let i = 0; i < str.length; i++) {
|
|
79
|
-
if (str[i] === '\n') {
|
|
80
|
-
result.push(current);
|
|
81
|
-
current = '';
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
current += str[i];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
result.push(current);
|
|
88
|
-
return result;
|
|
76
|
+
return str.split('\n');
|
|
89
77
|
}
|
|
90
78
|
/**
|
|
91
79
|
* Trim whitespace from string.
|
|
@@ -94,17 +82,7 @@ export function splitByNewline(str) {
|
|
|
94
82
|
* @returns Trimmed string.
|
|
95
83
|
*/
|
|
96
84
|
export function trimString(str) {
|
|
97
|
-
|
|
98
|
-
let end = str.length - 1;
|
|
99
|
-
while (start <= end &&
|
|
100
|
-
(str[start] === ' ' || str[start] === '\t' || str[start] === '\r' || str[start] === '\n')) {
|
|
101
|
-
start++;
|
|
102
|
-
}
|
|
103
|
-
while (end >= start &&
|
|
104
|
-
(str[end] === ' ' || str[end] === '\t' || str[end] === '\r' || str[end] === '\n')) {
|
|
105
|
-
end--;
|
|
106
|
-
}
|
|
107
|
-
return str.substring(start, end + 1);
|
|
85
|
+
return str.trim();
|
|
108
86
|
}
|
|
109
87
|
/**
|
|
110
88
|
* Parse relation line and extract components.
|
|
@@ -113,13 +91,13 @@ export function trimString(str) {
|
|
|
113
91
|
* @returns Parsed relation or null if not a relation line.
|
|
114
92
|
*/
|
|
115
93
|
export function parseRelationLine(line) {
|
|
116
|
-
if (!startsWith(
|
|
94
|
+
if (!line.startsWith('@relation'))
|
|
117
95
|
return null;
|
|
118
|
-
const parts =
|
|
96
|
+
const parts = line.trim().split(/\s+/);
|
|
119
97
|
if (parts.length < 5)
|
|
120
98
|
return null;
|
|
121
|
-
const fromParts =
|
|
122
|
-
const toParts =
|
|
99
|
+
const fromParts = parts[1].split('.');
|
|
100
|
+
const toParts = parts[2].split('.');
|
|
123
101
|
if (fromParts.length !== 2 || toParts.length !== 2)
|
|
124
102
|
return null;
|
|
125
103
|
return {
|
|
@@ -159,28 +137,10 @@ export function removeOptionalSuffix(str) {
|
|
|
159
137
|
* @returns Array of strings split by whitespace.
|
|
160
138
|
*/
|
|
161
139
|
export function splitByWhitespace(str) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const char = str[i];
|
|
167
|
-
const isWhitespace = char === ' ' || char === '\t' || char === '\r' || char === '\n';
|
|
168
|
-
if (isWhitespace) {
|
|
169
|
-
if (inWord) {
|
|
170
|
-
result.push(current);
|
|
171
|
-
current = '';
|
|
172
|
-
inWord = false;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
current += char;
|
|
177
|
-
inWord = true;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (inWord) {
|
|
181
|
-
result.push(current);
|
|
182
|
-
}
|
|
183
|
-
return result;
|
|
140
|
+
return str
|
|
141
|
+
.trim()
|
|
142
|
+
.split(/\s+/)
|
|
143
|
+
.filter((s) => s.length > 0);
|
|
184
144
|
}
|
|
185
145
|
/**
|
|
186
146
|
* Split string by dot character.
|
|
@@ -189,19 +149,7 @@ export function splitByWhitespace(str) {
|
|
|
189
149
|
* @returns Array of strings split by dot.
|
|
190
150
|
*/
|
|
191
151
|
export function splitByDot(str) {
|
|
192
|
-
|
|
193
|
-
let current = '';
|
|
194
|
-
for (let i = 0; i < str.length; i++) {
|
|
195
|
-
if (str[i] === '.') {
|
|
196
|
-
result.push(current);
|
|
197
|
-
current = '';
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
current += str[i];
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
result.push(current);
|
|
204
|
-
return result;
|
|
152
|
+
return str.split('.');
|
|
205
153
|
}
|
|
206
154
|
/* ========================================================================== *
|
|
207
155
|
* parse
|
|
@@ -214,24 +162,25 @@ export function splitByDot(str) {
|
|
|
214
162
|
* @returns Parsed definition and description
|
|
215
163
|
*/
|
|
216
164
|
export function parseFieldComments(commentLines, tag) {
|
|
217
|
-
const cleaned = commentLines
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const objectType = objectTypeLine
|
|
222
|
-
? '
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
165
|
+
const cleaned = commentLines
|
|
166
|
+
.map((line) => (line.startsWith('///') ? line.substring(3) : line).trim())
|
|
167
|
+
.filter((line) => line.length > 0);
|
|
168
|
+
const objectTypeLine = cleaned.find((line) => line.includes(`${tag.slice(1)}strictObject`) || line.includes(`${tag.slice(1)}looseObject`));
|
|
169
|
+
const objectType = objectTypeLine
|
|
170
|
+
? objectTypeLine.includes('strictObject')
|
|
171
|
+
? 'strict'
|
|
172
|
+
: objectTypeLine.includes('looseObject')
|
|
173
|
+
? 'loose'
|
|
174
|
+
: undefined
|
|
175
|
+
: undefined;
|
|
176
|
+
const definitionLine = cleaned.find((line) => line.startsWith(tag) && !line.includes('strictObject') && !line.includes('looseObject'));
|
|
177
|
+
const definition = definitionLine
|
|
178
|
+
? definitionLine.startsWith('@')
|
|
179
|
+
? definitionLine.substring(1)
|
|
180
|
+
: definitionLine
|
|
181
|
+
: '';
|
|
182
|
+
const descriptionLines = cleaned.filter((line) => !(line.includes('@z.') || line.includes('@v.') || line.includes('@relation.')));
|
|
183
|
+
const description = descriptionLines.length > 0 ? descriptionLines.join(' ') : undefined;
|
|
235
184
|
return { definition, description, objectType };
|
|
236
185
|
}
|
|
237
186
|
/* ========================================================================== *
|
|
@@ -246,14 +195,14 @@ export function parseFieldComments(commentLines, tag) {
|
|
|
246
195
|
*/
|
|
247
196
|
export function extractFieldComments(sourceText, fieldStartPos) {
|
|
248
197
|
const beforeField = sourceText.substring(0, fieldStartPos);
|
|
249
|
-
const lines =
|
|
198
|
+
const lines = beforeField.split('\n');
|
|
250
199
|
const reverseIndex = lines
|
|
251
|
-
.map((line, index) => ({ line:
|
|
200
|
+
.map((line, index) => ({ line: line.trim(), index }))
|
|
252
201
|
.reverse()
|
|
253
202
|
.reduce((acc, { line }) => {
|
|
254
203
|
if (acc.shouldStop)
|
|
255
204
|
return acc;
|
|
256
|
-
if (startsWith(
|
|
205
|
+
if (line.startsWith('///')) {
|
|
257
206
|
return {
|
|
258
207
|
commentLines: [line, ...acc.commentLines],
|
|
259
208
|
shouldStop: false,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sizuku",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Sizuku is a tool that generates validation schemas for Zod and Valibot, as well as ER diagrams, from Drizzle schemas annotated with comments.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
"bin": {
|
|
30
30
|
"sizuku": "dist/index.js"
|
|
31
31
|
},
|
|
32
|
+
"exports": {
|
|
33
|
+
"./config": {
|
|
34
|
+
"types": "./dist/config/index.d.ts",
|
|
35
|
+
"import": "./dist/config/index.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
32
38
|
"scripts": {
|
|
33
39
|
"deps": "rm -rf node_modules && pnpm install",
|
|
34
40
|
"build": "tsc",
|
|
@@ -48,6 +54,7 @@
|
|
|
48
54
|
},
|
|
49
55
|
"dependencies": {
|
|
50
56
|
"prettier": "^3.6.2",
|
|
51
|
-
"ts-morph": "^26.0.0"
|
|
57
|
+
"ts-morph": "^26.0.0",
|
|
58
|
+
"tsx": "^4.20.5"
|
|
52
59
|
}
|
|
53
60
|
}
|