sizuku 0.1.0 → 0.2.1
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 +18 -12
- package/dist/cli/main.d.ts +10 -0
- package/dist/cli/main.js +61 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +51 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/loader.d.ts +19 -0
- package/dist/config/loader.js +30 -0
- package/dist/generator/engine.d.ts +3 -0
- package/dist/generator/engine.js +63 -0
- package/dist/generator/mermaid-er/core.d.ts +6 -0
- package/dist/generator/mermaid-er/core.js +54 -0
- package/dist/generator/mermaid-er/generator/er-content.d.ts +14 -2
- package/dist/generator/mermaid-er/generator/index.d.ts +1 -1
- package/dist/generator/mermaid-er/generator/index.js +1 -1
- package/dist/generator/mermaid-er/generator/relation-line.d.ts +7 -2
- package/dist/generator/mermaid-er/generator/relation-line.js +1 -1
- package/dist/generator/mermaid-er/generator.d.ts +3 -0
- package/dist/generator/mermaid-er/generator.js +14 -0
- package/dist/generator/mermaid-er/index.d.ts +7 -1
- package/dist/generator/mermaid-er/index.js +21 -4
- package/dist/generator/mermaid-er/validator/index.d.ts +8 -4
- package/dist/generator/mermaid-er/validator/index.js +71 -4
- package/dist/generator/mermaid-er/validator/parse-table-info.js +1 -1
- package/dist/generator/valibot/core.d.ts +5 -0
- package/dist/generator/valibot/core.js +39 -0
- package/dist/generator/valibot/generator/infer-input.js +1 -1
- package/dist/generator/valibot/generator/relation-valibot-code.d.ts +13 -0
- package/dist/generator/valibot/generator/relation-valibot-code.js +19 -0
- package/dist/generator/valibot/generator/valibot-code.d.ts +9 -2
- package/dist/generator/valibot/generator/valibot-code.js +1 -1
- package/dist/generator/valibot/generator/valibot.d.ts +9 -2
- package/dist/generator/valibot/generator/valibot.js +7 -3
- package/dist/generator/valibot/generator.d.ts +3 -0
- package/dist/generator/valibot/generator.js +14 -0
- package/dist/generator/valibot/index.d.ts +7 -2
- package/dist/generator/valibot/index.js +36 -8
- package/dist/generator/zod/core.d.ts +5 -0
- package/dist/generator/zod/core.js +39 -0
- package/dist/generator/zod/generator/infer.js +2 -2
- package/dist/generator/zod/generator/relation-zod-code.d.ts +13 -0
- package/dist/generator/zod/generator/relation-zod-code.js +19 -0
- package/dist/generator/zod/generator/zod-code.d.ts +9 -2
- package/dist/generator/zod/generator/zod-code.js +1 -1
- package/dist/generator/zod/generator/zod.d.ts +9 -2
- package/dist/generator/zod/generator/zod.js +7 -3
- package/dist/generator/zod/generator.d.ts +3 -0
- package/dist/generator/zod/generator.js +14 -0
- package/dist/generator/zod/index.d.ts +7 -2
- package/dist/generator/zod/index.js +40 -16
- package/dist/index.d.ts +7 -2
- package/dist/index.js +60 -43
- package/dist/shared/format/index.d.ts +15 -2
- package/dist/shared/format/index.js +22 -8
- package/dist/shared/fs/index.d.ts +7 -2
- package/dist/shared/fs/index.js +9 -3
- package/dist/shared/fsp/index.d.ts +27 -3
- package/dist/shared/fsp/index.js +35 -5
- package/dist/shared/generator/field-definitions.d.ts +8 -2
- package/dist/shared/helper/ast-parser.d.ts +3 -0
- package/dist/shared/helper/ast-parser.js +202 -0
- package/dist/shared/helper/build-schema-extractor.d.ts +25 -0
- package/dist/shared/helper/build-schema-extractor.js +33 -0
- package/dist/shared/helper/create-extract-field-from-property.d.ts +15 -0
- package/dist/shared/helper/create-extract-field-from-property.js +20 -0
- package/dist/shared/helper/create-extract-fields-from-call-expression.d.ts +14 -0
- package/dist/shared/helper/create-extract-fields-from-call-expression.js +14 -0
- package/dist/shared/helper/create-extract-relation-field-from-property.d.ts +12 -0
- package/dist/shared/helper/create-extract-relation-field-from-property.js +27 -0
- package/dist/shared/helper/extract-schemas.d.ts +133 -0
- package/dist/shared/helper/extract-schemas.js +445 -0
- package/dist/shared/helper/file-writer.d.ts +3 -0
- package/dist/shared/helper/file-writer.js +25 -0
- package/dist/shared/helper/find-object-literal-expression.d.ts +12 -0
- package/dist/shared/helper/find-object-literal-expression.js +31 -0
- package/dist/shared/helper/find-object-literalIn-args.d.ts +2 -0
- package/dist/shared/helper/find-object-literalIn-args.js +8 -0
- package/dist/shared/helper/is-relation-function.d.ts +10 -0
- package/dist/shared/helper/is-relation-function.js +16 -0
- package/dist/shared/utils/index.d.ts +20 -0
- package/dist/shared/utils/index.js +48 -0
- package/dist/shared/utils/string-utils.d.ts +8 -0
- package/dist/shared/utils/string-utils.js +28 -0
- package/dist/shared/utils/types.d.ts +32 -0
- package/dist/shared/utils/types.js +2 -0
- package/dist/shared/utils/validation-utils.d.ts +8 -0
- package/dist/shared/utils/validation-utils.js +25 -0
- package/dist/src/config/index.d.ts +18 -0
- package/dist/src/config/index.js +13 -0
- package/dist/src/generator/mermaid-er/core/extract-relations.d.ts +8 -0
- package/dist/src/generator/mermaid-er/core/extract-relations.js +12 -0
- package/dist/src/generator/mermaid-er/generator/er-content.d.ts +9 -0
- package/dist/src/generator/mermaid-er/generator/er-content.js +25 -0
- package/dist/src/generator/mermaid-er/generator/index.d.ts +2 -0
- package/dist/src/generator/mermaid-er/generator/index.js +2 -0
- package/dist/src/generator/mermaid-er/generator/relation-line.d.ts +8 -0
- package/dist/src/generator/mermaid-er/generator/relation-line.js +14 -0
- package/dist/src/generator/mermaid-er/index.d.ts +6 -0
- package/dist/src/generator/mermaid-er/index.js +16 -0
- package/dist/src/generator/mermaid-er/relationship/build-relation-line.d.ts +15 -0
- package/dist/src/generator/mermaid-er/relationship/build-relation-line.js +35 -0
- package/dist/src/generator/mermaid-er/types.d.ts +21 -0
- package/dist/src/generator/mermaid-er/types.js +1 -0
- package/dist/src/generator/mermaid-er/validator/index.d.ts +4 -0
- package/dist/src/generator/mermaid-er/validator/index.js +4 -0
- package/dist/src/generator/mermaid-er/validator/is-relationship.d.ts +8 -0
- package/dist/src/generator/mermaid-er/validator/is-relationship.js +9 -0
- package/dist/src/generator/mermaid-er/validator/parse-relation-line.d.ts +13 -0
- package/dist/src/generator/mermaid-er/validator/parse-relation-line.js +21 -0
- package/dist/src/generator/mermaid-er/validator/parse-table-info.d.ts +8 -0
- package/dist/src/generator/mermaid-er/validator/parse-table-info.js +91 -0
- package/dist/src/generator/mermaid-er/validator/remove-duplicate-relations.d.ts +7 -0
- package/dist/src/generator/mermaid-er/validator/remove-duplicate-relations.js +9 -0
- package/dist/src/generator/valibot/generator/infer-input.d.ts +5 -0
- package/dist/src/generator/valibot/generator/infer-input.js +8 -0
- package/dist/src/generator/valibot/generator/valibot-code.d.ts +14 -0
- package/dist/src/generator/valibot/generator/valibot-code.js +16 -0
- package/dist/src/generator/valibot/generator/valibot.d.ts +13 -0
- package/dist/src/generator/valibot/generator/valibot.js +11 -0
- package/dist/src/generator/valibot/index.d.ts +9 -0
- package/dist/src/generator/valibot/index.js +34 -0
- package/dist/src/generator/zod/generator/infer.d.ts +5 -0
- package/dist/src/generator/zod/generator/infer.js +8 -0
- package/dist/src/generator/zod/generator/zod-code.d.ts +16 -0
- package/dist/src/generator/zod/generator/zod-code.js +18 -0
- package/dist/src/generator/zod/generator/zod.d.ts +15 -0
- package/dist/src/generator/zod/generator/zod.js +12 -0
- package/dist/src/generator/zod/index.d.ts +10 -0
- package/dist/src/generator/zod/index.js +40 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +35 -0
- package/dist/src/shared/format/index.d.ts +2 -0
- package/dist/src/shared/format/index.js +10 -0
- package/dist/src/shared/fs/index.d.ts +2 -0
- package/dist/src/shared/fs/index.js +10 -0
- package/dist/src/shared/fsp/index.d.ts +3 -0
- package/dist/src/shared/fsp/index.js +8 -0
- package/dist/src/shared/generator/field-definitions.d.ts +12 -0
- package/dist/src/shared/generator/field-definitions.js +12 -0
- package/dist/src/shared/helper/build-schema-extractor.d.ts +25 -0
- package/dist/src/shared/helper/build-schema-extractor.js +33 -0
- package/dist/src/shared/helper/create-extract-field-from-property.d.ts +15 -0
- package/dist/src/shared/helper/create-extract-field-from-property.js +20 -0
- package/dist/src/shared/helper/create-extract-fields-from-call-expression.d.ts +14 -0
- package/dist/src/shared/helper/create-extract-fields-from-call-expression.js +14 -0
- package/dist/src/shared/helper/create-extract-relation-field-from-property.d.ts +12 -0
- package/dist/src/shared/helper/create-extract-relation-field-from-property.js +27 -0
- package/dist/src/shared/helper/extract-schemas.d.ts +16 -0
- package/dist/src/shared/helper/extract-schemas.js +19 -0
- package/dist/src/shared/helper/find-object-literal-expression.d.ts +12 -0
- package/dist/src/shared/helper/find-object-literal-expression.js +31 -0
- package/dist/src/shared/helper/find-object-literalIn-args.d.ts +2 -0
- package/dist/src/shared/helper/find-object-literalIn-args.js +8 -0
- package/dist/src/shared/helper/is-relation-function.d.ts +10 -0
- package/dist/src/shared/helper/is-relation-function.js +16 -0
- package/dist/src/shared/utils/index.d.ts +33 -0
- package/dist/src/shared/utils/index.js +61 -0
- package/dist/utils/index.d.ts +144 -0
- package/dist/utils/index.js +301 -0
- package/package.json +2 -6
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ npm install -D sizuku
|
|
|
24
24
|
Prepare schema.ts:
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
|
-
import { mysqlTable, varchar } from 'drizzle-orm/mysql-core'
|
|
28
27
|
import { relations } from 'drizzle-orm'
|
|
28
|
+
import { mysqlTable, varchar } from 'drizzle-orm/mysql-core'
|
|
29
29
|
|
|
30
30
|
export const user = mysqlTable('user', {
|
|
31
31
|
/// Primary key
|
|
@@ -49,8 +49,8 @@ export const post = mysqlTable('post', {
|
|
|
49
49
|
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(100))
|
|
50
50
|
title: varchar('title', { length: 100 }).notNull(),
|
|
51
51
|
/// Body content (no length limit)
|
|
52
|
-
/// @z.string()
|
|
53
|
-
/// @v.string()
|
|
52
|
+
/// @z.string().min(1).max(65535)
|
|
53
|
+
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(65535))
|
|
54
54
|
content: varchar('content', { length: 65535 }).notNull(),
|
|
55
55
|
/// Foreign key referencing User.id
|
|
56
56
|
/// @z.uuid()
|
|
@@ -99,7 +99,7 @@ npx sizuku
|
|
|
99
99
|
### Zod
|
|
100
100
|
|
|
101
101
|
```ts
|
|
102
|
-
import
|
|
102
|
+
import * as z from 'zod'
|
|
103
103
|
|
|
104
104
|
export const UserSchema = z.object({
|
|
105
105
|
/**
|
|
@@ -126,7 +126,7 @@ export const PostSchema = z.object({
|
|
|
126
126
|
/**
|
|
127
127
|
* Body content (no length limit)
|
|
128
128
|
*/
|
|
129
|
-
content: z.string(),
|
|
129
|
+
content: z.string().min(1).max(65535),
|
|
130
130
|
/**
|
|
131
131
|
* Foreign key referencing User.id
|
|
132
132
|
*/
|
|
@@ -134,6 +134,14 @@ export const PostSchema = z.object({
|
|
|
134
134
|
})
|
|
135
135
|
|
|
136
136
|
export type Post = z.infer<typeof PostSchema>
|
|
137
|
+
|
|
138
|
+
export const UserRelationsSchema = z.object({ ...UserSchema.shape, posts: z.array(PostSchema) })
|
|
139
|
+
|
|
140
|
+
export type UserRelations = z.infer<typeof UserRelationsSchema>
|
|
141
|
+
|
|
142
|
+
export const PostRelationsSchema = z.object({ ...PostSchema.shape, user: UserSchema })
|
|
143
|
+
|
|
144
|
+
export type PostRelations = z.infer<typeof PostRelationsSchema>
|
|
137
145
|
```
|
|
138
146
|
|
|
139
147
|
### Valibot
|
|
@@ -152,8 +160,6 @@ export const UserSchema = v.object({
|
|
|
152
160
|
name: v.pipe(v.string(), v.minLength(1), v.maxLength(50)),
|
|
153
161
|
})
|
|
154
162
|
|
|
155
|
-
export type User = v.InferInput<typeof UserSchema>
|
|
156
|
-
|
|
157
163
|
export const PostSchema = v.object({
|
|
158
164
|
/**
|
|
159
165
|
* Primary key
|
|
@@ -166,14 +172,16 @@ export const PostSchema = v.object({
|
|
|
166
172
|
/**
|
|
167
173
|
* Body content (no length limit)
|
|
168
174
|
*/
|
|
169
|
-
content: v.string(),
|
|
175
|
+
content: v.pipe(v.string(), v.minLength(1), v.maxLength(65535)),
|
|
170
176
|
/**
|
|
171
177
|
* Foreign key referencing User.id
|
|
172
178
|
*/
|
|
173
179
|
userId: v.pipe(v.string(), v.uuid()),
|
|
174
180
|
})
|
|
175
181
|
|
|
176
|
-
export
|
|
182
|
+
export const UserRelationsSchema = v.object({ ...UserSchema.entries, posts: v.array(PostSchema) })
|
|
183
|
+
|
|
184
|
+
export const PostRelationsSchema = v.object({ ...PostSchema.entries, user: UserSchema })
|
|
177
185
|
```
|
|
178
186
|
|
|
179
187
|
### Mermaid ER
|
|
@@ -193,11 +201,9 @@ erDiagram
|
|
|
193
201
|
}
|
|
194
202
|
```
|
|
195
203
|
|
|
196
|
-
This project is in **early development** and being maintained by a developer with about 2 years of experience. While I'm doing my best to create a useful tool:
|
|
197
|
-
|
|
198
204
|
### ⚠️ WARNING: Potential Breaking Changes Without Notice
|
|
199
205
|
|
|
200
|
-
|
|
206
|
+
This package is in active development and may introduce breaking changes without prior notice.
|
|
201
207
|
|
|
202
208
|
## License
|
|
203
209
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import type { Result } from 'neverthrow';
|
|
3
|
+
import type { Config } from '../src/config/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Main CLI function that orchestrates the schema generation process.
|
|
6
|
+
*
|
|
7
|
+
* @param config - The configuration object.
|
|
8
|
+
* @returns A Result indicating success or failure.
|
|
9
|
+
*/
|
|
10
|
+
export declare function main(config?: Config): Promise<Result<void, Error>>;
|
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { err, ok, ResultAsync } from 'neverthrow';
|
|
3
|
+
import { getConfig } from '../src/config/index.js';
|
|
4
|
+
import { sizukuMermaidER } from '../src/generator/mermaid-er/index.js';
|
|
5
|
+
import { sizukuValibot } from '../src/generator/valibot/index.js';
|
|
6
|
+
import { sizukuZod } from '../src/generator/zod/index.js';
|
|
7
|
+
import { readFileSync } from '../src/shared/fs/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Main CLI function that orchestrates the schema generation process.
|
|
10
|
+
*
|
|
11
|
+
* @param config - The configuration object.
|
|
12
|
+
* @returns A Result indicating success or failure.
|
|
13
|
+
*/
|
|
14
|
+
export async function main(config = getConfig()) {
|
|
15
|
+
return ResultAsync.fromPromise(Promise.resolve(), () => new Error('init'))
|
|
16
|
+
.andThen(() => {
|
|
17
|
+
if (!config.input) {
|
|
18
|
+
return err(new Error('input is not found'));
|
|
19
|
+
}
|
|
20
|
+
const contentResult = readFileSync(config.input);
|
|
21
|
+
if (contentResult.isErr()) {
|
|
22
|
+
return err(contentResult.error);
|
|
23
|
+
}
|
|
24
|
+
const content = contentResult.value;
|
|
25
|
+
const lines = content.split('\n');
|
|
26
|
+
const codeStart = lines.findIndex((line) => !line.trim().startsWith('import') && line.trim() !== '');
|
|
27
|
+
return ok(lines.slice(codeStart));
|
|
28
|
+
})
|
|
29
|
+
.andThen((code) => {
|
|
30
|
+
if (config.zod?.output) {
|
|
31
|
+
return ResultAsync.fromPromise(sizukuZod(code, config.zod.output, config.zod.comment, config.zod.type, config.zod.zod), (e) => (e instanceof Error ? e : new Error(String(e)))).map(() => {
|
|
32
|
+
console.log(`Generated Zod schema at: ${config.zod?.output}`);
|
|
33
|
+
return code;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return ok(code);
|
|
37
|
+
})
|
|
38
|
+
.andThen((code) => {
|
|
39
|
+
if (config.valibot?.output) {
|
|
40
|
+
return ResultAsync.fromPromise(sizukuValibot(code, config.valibot.output, config.valibot.comment, config.valibot.type), (e) => (e instanceof Error ? e : new Error(String(e)))).map(() => {
|
|
41
|
+
console.log(`Generated Valibot schema at: ${config.valibot?.output}`);
|
|
42
|
+
return code;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return ok(code);
|
|
46
|
+
})
|
|
47
|
+
.andThen((code) => {
|
|
48
|
+
if (config.mermaid?.output) {
|
|
49
|
+
return ResultAsync.fromPromise(sizukuMermaidER(code, config.mermaid.output), (e) => e instanceof Error ? e : new Error(String(e))).map(() => {
|
|
50
|
+
console.log(`Generated Mermaid ER at: ${config.mermaid?.output}`);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return ok(undefined);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
main().then((result) => {
|
|
57
|
+
if (!result.isOk()) {
|
|
58
|
+
console.error(result.error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { Result, err, ok } from 'neverthrow';
|
|
4
|
+
import { loadConfig, validateConfig } from './config/loader.js';
|
|
5
|
+
import { generateSchemas } from './generator/engine.js';
|
|
6
|
+
const main = async () => {
|
|
7
|
+
try {
|
|
8
|
+
// コマンドライン引数を解析
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
if (args.length === 0) {
|
|
11
|
+
return err(new Error('Usage: sizuku <config-file>'));
|
|
12
|
+
}
|
|
13
|
+
const configPath = resolve(args[0]);
|
|
14
|
+
// 設定ファイルを読み込み
|
|
15
|
+
const configResult = await loadConfig(configPath);
|
|
16
|
+
if (configResult.isErr()) {
|
|
17
|
+
return err(configResult.error);
|
|
18
|
+
}
|
|
19
|
+
// 設定をバリデーション
|
|
20
|
+
const validationResult = validateConfig(configResult.value);
|
|
21
|
+
if (validationResult.isErr()) {
|
|
22
|
+
return err(validationResult.error);
|
|
23
|
+
}
|
|
24
|
+
const config = validationResult.value;
|
|
25
|
+
// スキーマ生成を実行
|
|
26
|
+
const generationResult = await generateSchemas(config);
|
|
27
|
+
if (generationResult.isErr()) {
|
|
28
|
+
return err(generationResult.error);
|
|
29
|
+
}
|
|
30
|
+
console.log('✅ Schema generation completed successfully!');
|
|
31
|
+
return ok(undefined);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return err(new Error(`CLI execution failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
// CLI実行
|
|
38
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
39
|
+
main()
|
|
40
|
+
.then(result => {
|
|
41
|
+
if (result.isErr()) {
|
|
42
|
+
console.error('❌ Error:', result.error.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
process.exit(0);
|
|
46
|
+
})
|
|
47
|
+
.catch(error => {
|
|
48
|
+
console.error('❌ Unexpected error:', error instanceof Error ? error.message : String(error));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
});
|
|
51
|
+
}
|
package/dist/config/index.d.ts
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Result } from 'neverthrow';
|
|
2
|
+
export type SizukuConfig = {
|
|
3
|
+
input: string;
|
|
4
|
+
zod?: {
|
|
5
|
+
output: string;
|
|
6
|
+
comment?: boolean;
|
|
7
|
+
type?: boolean;
|
|
8
|
+
};
|
|
9
|
+
valibot?: {
|
|
10
|
+
output: string;
|
|
11
|
+
comment?: boolean;
|
|
12
|
+
type?: boolean;
|
|
13
|
+
};
|
|
14
|
+
mermaid?: {
|
|
15
|
+
output: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare const loadConfig: (configPath: string) => Promise<Result<SizukuConfig, Error>>;
|
|
19
|
+
export declare const validateConfig: (config: SizukuConfig) => Result<SizukuConfig, Error>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { Result, err, ok } from 'neverthrow';
|
|
3
|
+
export const loadConfig = async (configPath) => {
|
|
4
|
+
try {
|
|
5
|
+
const configContent = await readFile(configPath, 'utf-8');
|
|
6
|
+
const config = JSON.parse(configContent);
|
|
7
|
+
// 基本的なバリデーション
|
|
8
|
+
if (!config.input) {
|
|
9
|
+
return err(new Error('Config must specify input file path'));
|
|
10
|
+
}
|
|
11
|
+
return ok(config);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
return err(new Error(`Failed to load config from ${configPath}: ${error instanceof Error ? error.message : String(error)}`));
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export const validateConfig = (config) => {
|
|
18
|
+
try {
|
|
19
|
+
if (!config.input) {
|
|
20
|
+
return err(new Error('Input file path is required'));
|
|
21
|
+
}
|
|
22
|
+
if (!config.zod && !config.valibot && !config.mermaid) {
|
|
23
|
+
return err(new Error('At least one output format must be specified (zod, valibot, or mermaid)'));
|
|
24
|
+
}
|
|
25
|
+
return ok(config);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return err(new Error(`Config validation failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
29
|
+
}
|
|
30
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Result, err, ok } from 'neverthrow';
|
|
2
|
+
import { parseSchemaFile } from '../shared/helper/ast-parser.js';
|
|
3
|
+
import { generateZodSchemas } from './zod/generator.js';
|
|
4
|
+
import { generateValibotSchemas } from './valibot/generator.js';
|
|
5
|
+
import { generateMermaidERDiagram } from './mermaid-er/generator.js';
|
|
6
|
+
import { writeFileSafely } from '../shared/helper/file-writer.js';
|
|
7
|
+
export const generateSchemas = async (config) => {
|
|
8
|
+
try {
|
|
9
|
+
// スキーマファイルを解析
|
|
10
|
+
const parseResult = await parseSchemaFile(config.input);
|
|
11
|
+
if (parseResult.isErr()) {
|
|
12
|
+
return err(parseResult.error);
|
|
13
|
+
}
|
|
14
|
+
const schema = parseResult.value;
|
|
15
|
+
// 各フォーマットを生成
|
|
16
|
+
const results = [];
|
|
17
|
+
if (config.zod) {
|
|
18
|
+
results.push(generateZodOutput(schema, config.zod.output));
|
|
19
|
+
}
|
|
20
|
+
if (config.valibot) {
|
|
21
|
+
results.push(generateValibotOutput(schema, config.valibot.output));
|
|
22
|
+
}
|
|
23
|
+
if (config.mermaid) {
|
|
24
|
+
results.push(generateMermaidOutput(schema, config.mermaid.output));
|
|
25
|
+
}
|
|
26
|
+
// すべての生成を待機
|
|
27
|
+
const generationResults = await Promise.all(results);
|
|
28
|
+
// エラーがあれば最初のエラーを返す
|
|
29
|
+
for (const result of generationResults) {
|
|
30
|
+
if (result.isErr()) {
|
|
31
|
+
return err(result.error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return ok(undefined);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return err(new Error(`Schema generation failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const generateZodOutput = async (schema, outputPath) => {
|
|
41
|
+
const zodResult = generateZodSchemas(schema);
|
|
42
|
+
if (zodResult.isErr()) {
|
|
43
|
+
return err(zodResult.error);
|
|
44
|
+
}
|
|
45
|
+
const writeResult = await writeFileSafely(outputPath, zodResult.value);
|
|
46
|
+
return writeResult;
|
|
47
|
+
};
|
|
48
|
+
const generateValibotOutput = async (schema, outputPath) => {
|
|
49
|
+
const valibotResult = generateValibotSchemas(schema);
|
|
50
|
+
if (valibotResult.isErr()) {
|
|
51
|
+
return err(valibotResult.error);
|
|
52
|
+
}
|
|
53
|
+
const writeResult = await writeFileSafely(outputPath, valibotResult.value);
|
|
54
|
+
return writeResult;
|
|
55
|
+
};
|
|
56
|
+
const generateMermaidOutput = async (schema, outputPath) => {
|
|
57
|
+
const mermaidResult = generateMermaidERDiagram(schema);
|
|
58
|
+
if (mermaidResult.isErr()) {
|
|
59
|
+
return err(mermaidResult.error);
|
|
60
|
+
}
|
|
61
|
+
const writeResult = await writeFileSafely(outputPath, mermaidResult.value);
|
|
62
|
+
return writeResult;
|
|
63
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TableInfo, ColumnInfo, RelationInfo } from '../../shared/utils/types.js';
|
|
2
|
+
export declare const generateMermaidER: (tables: TableInfo[]) => string;
|
|
3
|
+
export declare const generateRelations: (tables: TableInfo[]) => string;
|
|
4
|
+
export declare const generateRelationLine: (relation: RelationInfo) => string;
|
|
5
|
+
export declare const generateEntity: (table: TableInfo) => string;
|
|
6
|
+
export declare const generateColumnDefinition: (column: ColumnInfo) => string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// 完全純粋なMermaid ER図生成ユーティリティ - 外部import禁止
|
|
2
|
+
export const generateMermaidER = (tables) => {
|
|
3
|
+
const header = 'erDiagram';
|
|
4
|
+
const relations = generateRelations(tables);
|
|
5
|
+
const entities = tables.map(table => generateEntity(table)).join('\n ');
|
|
6
|
+
return `${header}
|
|
7
|
+
${relations}
|
|
8
|
+
${entities}`;
|
|
9
|
+
};
|
|
10
|
+
export const generateRelations = (tables) => {
|
|
11
|
+
const relations = [];
|
|
12
|
+
for (const table of tables) {
|
|
13
|
+
for (const relation of table.relations) {
|
|
14
|
+
const relationLine = generateRelationLine(relation);
|
|
15
|
+
if (relationLine) {
|
|
16
|
+
relations.push(relationLine);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return relations.join('\n ');
|
|
21
|
+
};
|
|
22
|
+
export const generateRelationLine = (relation) => {
|
|
23
|
+
const sourceTable = relation.sourceTable;
|
|
24
|
+
const targetTable = relation.targetTable;
|
|
25
|
+
const sourceColumn = relation.sourceColumn;
|
|
26
|
+
const targetColumn = relation.targetColumn;
|
|
27
|
+
if (relation.type === 'one-to-many') {
|
|
28
|
+
return `${sourceTable} ||--}| ${targetTable} : "(${sourceColumn}) - (${targetColumn})"`;
|
|
29
|
+
}
|
|
30
|
+
else if (relation.type === 'many-to-one') {
|
|
31
|
+
return `${targetTable} ||--}| ${sourceTable} : "(${targetColumn}) - (${sourceColumn})"`;
|
|
32
|
+
}
|
|
33
|
+
else if (relation.type === 'one-to-one') {
|
|
34
|
+
return `${sourceTable} ||--|| ${targetTable} : "(${sourceColumn}) - (${targetColumn})"`;
|
|
35
|
+
}
|
|
36
|
+
else if (relation.type === 'many-to-many') {
|
|
37
|
+
return `${sourceTable} }|--|{ ${targetTable} : "(${sourceColumn}) - (${targetColumn})"`;
|
|
38
|
+
}
|
|
39
|
+
return '';
|
|
40
|
+
};
|
|
41
|
+
export const generateEntity = (table) => {
|
|
42
|
+
const columns = table.columns.map(column => generateColumnDefinition(column)).join('\n ');
|
|
43
|
+
return `${table.name} {
|
|
44
|
+
${columns}
|
|
45
|
+
}`;
|
|
46
|
+
};
|
|
47
|
+
export const generateColumnDefinition = (column) => {
|
|
48
|
+
const type = column.type;
|
|
49
|
+
const name = column.name;
|
|
50
|
+
const comment = column.comment || '';
|
|
51
|
+
const primaryKey = column.isPrimary ? ' (PK)' : '';
|
|
52
|
+
const nullable = column.isNullable ? '' : ' NOT NULL';
|
|
53
|
+
return `${type} ${name}${primaryKey}${nullable} "${comment}"`;
|
|
54
|
+
};
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
import type { Relation, TableInfo } from '../types.js';
|
|
2
1
|
/**
|
|
3
2
|
* Generate ER content
|
|
4
3
|
* @param relations - The relations to generate the ER content from
|
|
5
4
|
* @param tables - The tables to generate the ER content from
|
|
6
5
|
* @returns The generated ER content
|
|
7
6
|
*/
|
|
8
|
-
export declare function erContent(relations:
|
|
7
|
+
export declare function erContent(relations: {
|
|
8
|
+
fromModel: string;
|
|
9
|
+
toModel: string;
|
|
10
|
+
fromField: string;
|
|
11
|
+
toField: string;
|
|
12
|
+
type: string;
|
|
13
|
+
}[], tables: {
|
|
14
|
+
name: string;
|
|
15
|
+
fields: {
|
|
16
|
+
type: string;
|
|
17
|
+
name: string;
|
|
18
|
+
description: string | null;
|
|
19
|
+
}[];
|
|
20
|
+
}[]): string;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type { Relation } from '../types.js';
|
|
2
1
|
/**
|
|
3
2
|
* Generate a relation line for a relation
|
|
4
3
|
* @param relation - The relation to generate a line for
|
|
5
4
|
* @returns The generated relation line
|
|
6
5
|
*/
|
|
7
|
-
export declare function relationLine(relation:
|
|
6
|
+
export declare function relationLine(relation: {
|
|
7
|
+
fromModel: string;
|
|
8
|
+
toModel: string;
|
|
9
|
+
fromField: string;
|
|
10
|
+
toField: string;
|
|
11
|
+
type: string;
|
|
12
|
+
}): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Result, err, ok } from 'neverthrow';
|
|
2
|
+
import { generateMermaidER } from './core.js';
|
|
3
|
+
export const generateMermaidERDiagram = (schema) => {
|
|
4
|
+
try {
|
|
5
|
+
if (schema.tables.length === 0) {
|
|
6
|
+
return err(new Error('No tables found in schema'));
|
|
7
|
+
}
|
|
8
|
+
const mermaidContent = generateMermaidER(schema.tables);
|
|
9
|
+
return ok(mermaidContent);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
return err(new Error(`Failed to generate Mermaid ER diagram: ${error instanceof Error ? error.message : String(error)}`));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -3,4 +3,10 @@
|
|
|
3
3
|
* @param code - The code to generate Mermaid ER diagram from
|
|
4
4
|
* @param output - The output file path
|
|
5
5
|
*/
|
|
6
|
-
export declare function sizukuMermaidER(code: string[], output: string): Promise<
|
|
6
|
+
export declare function sizukuMermaidER(code: string[], output: string): Promise<{
|
|
7
|
+
ok: true;
|
|
8
|
+
value: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
error: string;
|
|
12
|
+
}>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import { mkdir, writeFile } from '../../shared/fsp/index.js';
|
|
2
|
-
import { extractRelations } from '
|
|
3
|
+
import { extractRelations } from '../../shared/helper/extract-schemas.js';
|
|
3
4
|
import { erContent } from './generator/index.js';
|
|
4
|
-
import { parseTableInfo } from './validator/
|
|
5
|
-
import path from 'node:path';
|
|
5
|
+
import { parseTableInfo } from './validator/index.js';
|
|
6
6
|
/**
|
|
7
7
|
* Generate Mermaid ER diagram
|
|
8
8
|
* @param code - The code to generate Mermaid ER diagram from
|
|
@@ -12,5 +12,22 @@ export async function sizukuMermaidER(code, output) {
|
|
|
12
12
|
const tables = parseTableInfo(code);
|
|
13
13
|
const relations = extractRelations(code);
|
|
14
14
|
const ERContent = erContent(relations, tables);
|
|
15
|
-
|
|
15
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
16
|
+
if (!mkdirResult.ok) {
|
|
17
|
+
return {
|
|
18
|
+
ok: false,
|
|
19
|
+
error: mkdirResult.error,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const writeFileResult = await writeFile(output, ERContent);
|
|
23
|
+
if (!writeFileResult.ok) {
|
|
24
|
+
return {
|
|
25
|
+
ok: false,
|
|
26
|
+
error: writeFileResult.error,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
ok: true,
|
|
31
|
+
value: undefined,
|
|
32
|
+
};
|
|
16
33
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export declare function parseTableInfo(code: string[]): {
|
|
2
|
+
name: string;
|
|
3
|
+
fields: {
|
|
4
|
+
type: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string | null;
|
|
7
|
+
}[];
|
|
8
|
+
}[];
|
|
@@ -1,4 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { Node, Project } from 'ts-morph';
|
|
2
|
+
const baseBuilderName = (expr) => {
|
|
3
|
+
if (Node.isIdentifier(expr))
|
|
4
|
+
return expr.getText();
|
|
5
|
+
if (Node.isCallExpression(expr) || Node.isPropertyAccessExpression(expr))
|
|
6
|
+
return baseBuilderName(expr.getExpression());
|
|
7
|
+
return '';
|
|
8
|
+
};
|
|
9
|
+
const isFieldInfo = (v) => v !== null;
|
|
10
|
+
export function parseTableInfo(code) {
|
|
11
|
+
const source = code.join('\n');
|
|
12
|
+
const file = new Project({ useInMemoryFileSystem: true }).createSourceFile('temp.ts', source);
|
|
13
|
+
return file
|
|
14
|
+
.getVariableStatements()
|
|
15
|
+
.filter((stmt) => stmt.isExported())
|
|
16
|
+
.flatMap((stmt) => {
|
|
17
|
+
const decl = stmt.getDeclarations()[0];
|
|
18
|
+
if (!Node.isVariableDeclaration(decl))
|
|
19
|
+
return [];
|
|
20
|
+
const varName = decl.getName();
|
|
21
|
+
if (varName.toLowerCase().includes('relation'))
|
|
22
|
+
return [];
|
|
23
|
+
const init = decl.getInitializer();
|
|
24
|
+
if (!(init && Node.isCallExpression(init)))
|
|
25
|
+
return [];
|
|
26
|
+
const callee = init.getExpression().getText();
|
|
27
|
+
if (!callee.endsWith('Table') || callee === 'relations')
|
|
28
|
+
return [];
|
|
29
|
+
const objLit = init.getArguments()[1];
|
|
30
|
+
if (!(objLit && Node.isObjectLiteralExpression(objLit)))
|
|
31
|
+
return [];
|
|
32
|
+
const fields = objLit
|
|
33
|
+
.getProperties()
|
|
34
|
+
.filter(Node.isPropertyAssignment)
|
|
35
|
+
.map((prop) => {
|
|
36
|
+
const keyNode = prop.getNameNode();
|
|
37
|
+
if (!Node.isIdentifier(keyNode))
|
|
38
|
+
return null;
|
|
39
|
+
const fieldName = keyNode.getText();
|
|
40
|
+
const initExpr = prop.getInitializer();
|
|
41
|
+
if (!(initExpr && Node.isCallExpression(initExpr)))
|
|
42
|
+
return null;
|
|
43
|
+
const fieldType = baseBuilderName(initExpr);
|
|
44
|
+
const initText = initExpr.getText();
|
|
45
|
+
const lineIdx = prop.getStartLineNumber() - 1;
|
|
46
|
+
const baseDesc = code
|
|
47
|
+
.slice(0, lineIdx)
|
|
48
|
+
.reverse()
|
|
49
|
+
.find((line) => {
|
|
50
|
+
const t = line.trim();
|
|
51
|
+
return (t.startsWith('///') &&
|
|
52
|
+
!t.includes('@z.') &&
|
|
53
|
+
!t.includes('@v.') &&
|
|
54
|
+
!t.includes('@relation'));
|
|
55
|
+
})
|
|
56
|
+
?.replace(/^\s*\/\/\/\s*/, '') ?? '';
|
|
57
|
+
const prefix = initText.includes('.primaryKey()')
|
|
58
|
+
? '(PK) '
|
|
59
|
+
: initText.includes('.references(')
|
|
60
|
+
? '(FK) '
|
|
61
|
+
: '';
|
|
62
|
+
return {
|
|
63
|
+
name: fieldName,
|
|
64
|
+
type: fieldType,
|
|
65
|
+
description: `${prefix}${baseDesc}`.trim(),
|
|
66
|
+
};
|
|
67
|
+
})
|
|
68
|
+
.filter(isFieldInfo);
|
|
69
|
+
return [{ name: varName, fields }];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TableInfo, ColumnInfo } from '../../shared/utils/types.js';
|
|
2
|
+
export declare const generateValibotSchema: (table: TableInfo) => string;
|
|
3
|
+
export declare const generateValibotProperty: (column: ColumnInfo) => string;
|
|
4
|
+
export declare const generateDefaultValibotSchema: (column: ColumnInfo) => string;
|
|
5
|
+
export declare const generateValibotFile: (tables: TableInfo[]) => string;
|