sizuku 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -11
- 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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Node } from 'ts-morph';
|
|
2
|
+
/**
|
|
3
|
+
* Determines whether a given `CallExpression` is a relation-related function call.
|
|
4
|
+
*
|
|
5
|
+
* This checks if the function name is either `"relations"` or contains the substring `"relation"`.
|
|
6
|
+
*
|
|
7
|
+
* @param callExpr - The call expression node to check.
|
|
8
|
+
* @returns `true` if the function is a relation function; otherwise, `false`.
|
|
9
|
+
*/
|
|
10
|
+
export function isRelationFunctionCall(callExpr) {
|
|
11
|
+
const expression = callExpr.getExpression();
|
|
12
|
+
if (!Node.isIdentifier(expression))
|
|
13
|
+
return false;
|
|
14
|
+
const functionName = expression.getText();
|
|
15
|
+
return functionName === 'relations' || functionName.includes('relation');
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capitalize the first letter of a string.
|
|
3
|
+
*
|
|
4
|
+
* @param str - The input string.
|
|
5
|
+
* @returns A new string with the first letter capitalized.
|
|
6
|
+
*/
|
|
7
|
+
export declare function capitalize(str: string): string;
|
|
8
|
+
export declare function schemaName(str: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Parse field comments and extract definition line and description.
|
|
11
|
+
*
|
|
12
|
+
* @param commentLines - Raw comment lines (e.g., from source text)
|
|
13
|
+
* @param tag - The tag to look for (e.g., '@v.' or '@z.')
|
|
14
|
+
* @returns Parsed definition and description
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseFieldComments(commentLines: string[], tag: '@v.' | '@z.'): {
|
|
17
|
+
definition: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function extractFieldComments(sourceText: string, fieldStartPos: number): string[];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capitalize the first letter of a string.
|
|
3
|
+
*
|
|
4
|
+
* @param str - The input string.
|
|
5
|
+
* @returns A new string with the first letter capitalized.
|
|
6
|
+
*/
|
|
7
|
+
export function capitalize(str) {
|
|
8
|
+
return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
|
|
9
|
+
}
|
|
10
|
+
export function schemaName(str) {
|
|
11
|
+
return `${str.charAt(0).toUpperCase() + str.slice(1)}Schema`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse field comments and extract definition line and description.
|
|
15
|
+
*
|
|
16
|
+
* @param commentLines - Raw comment lines (e.g., from source text)
|
|
17
|
+
* @param tag - The tag to look for (e.g., '@v.' or '@z.')
|
|
18
|
+
* @returns Parsed definition and description
|
|
19
|
+
*/
|
|
20
|
+
export function parseFieldComments(commentLines, tag) {
|
|
21
|
+
const cleaned = commentLines.map((line) => line.replace(/^\/\/\/\s*/, '').trim()).filter(Boolean);
|
|
22
|
+
const definition = cleaned.find((line) => line.startsWith(tag))?.replace(/^@/, '') ?? '';
|
|
23
|
+
const descriptionLines = cleaned.filter((line) => !(line.includes('@z.') || line.includes('@v.') || line.includes('@relation.')));
|
|
24
|
+
const description = descriptionLines.length ? descriptionLines.join(' ') : undefined;
|
|
25
|
+
return { definition, description };
|
|
26
|
+
}
|
|
27
|
+
export function extractFieldComments(sourceText, fieldStartPos) {
|
|
28
|
+
const beforeField = sourceText.substring(0, fieldStartPos);
|
|
29
|
+
const lines = beforeField.split('\n');
|
|
30
|
+
const reverseIndex = lines
|
|
31
|
+
.map((line, index) => ({ line: line.trim(), index }))
|
|
32
|
+
.reverse()
|
|
33
|
+
.reduce((acc, { line }) => {
|
|
34
|
+
if (acc.shouldStop)
|
|
35
|
+
return acc;
|
|
36
|
+
if (line.startsWith('///')) {
|
|
37
|
+
return {
|
|
38
|
+
commentLines: [line, ...acc.commentLines],
|
|
39
|
+
shouldStop: false,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (line === '') {
|
|
43
|
+
return acc;
|
|
44
|
+
}
|
|
45
|
+
return { commentLines: acc.commentLines, shouldStop: true };
|
|
46
|
+
}, { commentLines: [], shouldStop: false });
|
|
47
|
+
return reverseIndex.commentLines;
|
|
48
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const capitalize: (str: string) => string;
|
|
2
|
+
export declare const toPascalCase: (str: string) => string;
|
|
3
|
+
export declare const extractComment: (commentText: string) => string;
|
|
4
|
+
export declare const parseZodTag: (tag: string) => string;
|
|
5
|
+
export declare const parseValibotTag: (tag: string) => string;
|
|
6
|
+
export declare const parseRelationTag: (tag: string) => string;
|
|
7
|
+
export declare const splitLines: (text: string) => string[];
|
|
8
|
+
export declare const joinLines: (lines: string[]) => string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// 完全純粋な文字列操作ユーティリティ - 外部import禁止
|
|
2
|
+
export const capitalize = (str) => {
|
|
3
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
4
|
+
};
|
|
5
|
+
export const toPascalCase = (str) => {
|
|
6
|
+
return str
|
|
7
|
+
.split(/[-_\s]+/)
|
|
8
|
+
.map(word => capitalize(word))
|
|
9
|
+
.join('');
|
|
10
|
+
};
|
|
11
|
+
export const extractComment = (commentText) => {
|
|
12
|
+
return commentText.replace(/^\/\/\/\s*/, '').trim();
|
|
13
|
+
};
|
|
14
|
+
export const parseZodTag = (tag) => {
|
|
15
|
+
return tag.replace(/^@z\./, '');
|
|
16
|
+
};
|
|
17
|
+
export const parseValibotTag = (tag) => {
|
|
18
|
+
return tag.replace(/^@v\./, '');
|
|
19
|
+
};
|
|
20
|
+
export const parseRelationTag = (tag) => {
|
|
21
|
+
return tag.replace(/^@relation\s+/, '');
|
|
22
|
+
};
|
|
23
|
+
export const splitLines = (text) => {
|
|
24
|
+
return text.split('\n').map(line => line.trim()).filter(line => line.length > 0);
|
|
25
|
+
};
|
|
26
|
+
export const joinLines = (lines) => {
|
|
27
|
+
return lines.join('\n');
|
|
28
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type TableInfo = {
|
|
2
|
+
name: string;
|
|
3
|
+
columns: ColumnInfo[];
|
|
4
|
+
relations: RelationInfo[];
|
|
5
|
+
};
|
|
6
|
+
export type ColumnInfo = {
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
isPrimary: boolean;
|
|
10
|
+
isNullable: boolean;
|
|
11
|
+
length?: number;
|
|
12
|
+
zodSchema?: string;
|
|
13
|
+
valibotSchema?: string;
|
|
14
|
+
comment?: string;
|
|
15
|
+
};
|
|
16
|
+
export type RelationInfo = {
|
|
17
|
+
sourceTable: string;
|
|
18
|
+
sourceColumn: string;
|
|
19
|
+
targetTable: string;
|
|
20
|
+
targetColumn: string;
|
|
21
|
+
type: 'one-to-many' | 'many-to-one' | 'one-to-one' | 'many-to-many';
|
|
22
|
+
};
|
|
23
|
+
export type ParsedSchema = {
|
|
24
|
+
tables: TableInfo[];
|
|
25
|
+
};
|
|
26
|
+
export type GenerationResult<T> = {
|
|
27
|
+
success: true;
|
|
28
|
+
data: T;
|
|
29
|
+
} | {
|
|
30
|
+
success: false;
|
|
31
|
+
error: string;
|
|
32
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ColumnInfo, RelationInfo } from './types.js';
|
|
2
|
+
export declare const validateTableName: (name: string) => boolean;
|
|
3
|
+
export declare const validateColumnName: (name: string) => boolean;
|
|
4
|
+
export declare const validateRelationType: (type: string) => type is "one-to-many" | "many-to-one" | "one-to-one" | "many-to-many";
|
|
5
|
+
export declare const validateSchema: (tables: ColumnInfo[][]) => boolean;
|
|
6
|
+
export declare const findPrimaryKey: (columns: ColumnInfo[]) => ColumnInfo | undefined;
|
|
7
|
+
export declare const findForeignKeys: (columns: ColumnInfo[]) => ColumnInfo[];
|
|
8
|
+
export declare const validateRelation: (relation: RelationInfo, tables: string[]) => boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// 完全純粋なバリデーション関連ユーティリティ - 外部import禁止
|
|
2
|
+
export const validateTableName = (name) => {
|
|
3
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name);
|
|
4
|
+
};
|
|
5
|
+
export const validateColumnName = (name) => {
|
|
6
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name);
|
|
7
|
+
};
|
|
8
|
+
export const validateRelationType = (type) => {
|
|
9
|
+
return ['one-to-many', 'many-to-one', 'one-to-one', 'many-to-many'].includes(type);
|
|
10
|
+
};
|
|
11
|
+
export const validateSchema = (tables) => {
|
|
12
|
+
return tables.every(table => table.every(column => validateColumnName(column.name) &&
|
|
13
|
+
column.type.length > 0));
|
|
14
|
+
};
|
|
15
|
+
export const findPrimaryKey = (columns) => {
|
|
16
|
+
return columns.find(column => column.isPrimary);
|
|
17
|
+
};
|
|
18
|
+
export const findForeignKeys = (columns) => {
|
|
19
|
+
return columns.filter(column => column.name.toLowerCase().includes('id') &&
|
|
20
|
+
!column.isPrimary);
|
|
21
|
+
};
|
|
22
|
+
export const validateRelation = (relation, tables) => {
|
|
23
|
+
return tables.includes(relation.sourceTable) &&
|
|
24
|
+
tables.includes(relation.targetTable);
|
|
25
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type Config = {
|
|
2
|
+
input?: `${string}.ts`;
|
|
3
|
+
zod?: {
|
|
4
|
+
output?: `${string}.ts`;
|
|
5
|
+
comment?: boolean;
|
|
6
|
+
type?: boolean;
|
|
7
|
+
zod?: 'v4' | 'mini' | '@hono/zod-openapi';
|
|
8
|
+
};
|
|
9
|
+
valibot?: {
|
|
10
|
+
output?: `${string}.ts`;
|
|
11
|
+
comment?: boolean;
|
|
12
|
+
type?: boolean;
|
|
13
|
+
};
|
|
14
|
+
mermaid?: {
|
|
15
|
+
output?: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare function getConfig(): Config;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
export function getConfig() {
|
|
3
|
+
if (!fs.existsSync('sizuku.json')) {
|
|
4
|
+
throw new Error('sizuku.json not found');
|
|
5
|
+
}
|
|
6
|
+
const parsed = JSON.parse(fs.readFileSync('sizuku.json', 'utf-8'));
|
|
7
|
+
return {
|
|
8
|
+
input: parsed.input,
|
|
9
|
+
zod: parsed.zod,
|
|
10
|
+
valibot: parsed.valibot,
|
|
11
|
+
mermaid: parsed.mermaid,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Relation } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts relations from the given code.
|
|
4
|
+
*
|
|
5
|
+
* @param code - The code to extract relations from.
|
|
6
|
+
* @returns The extracted relations.
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractRelations(code: string[]): Relation[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { parseRelationLine } from '../validator/parse-relation-line.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts relations from the given code.
|
|
4
|
+
*
|
|
5
|
+
* @param code - The code to extract relations from.
|
|
6
|
+
* @returns The extracted relations.
|
|
7
|
+
*/
|
|
8
|
+
export function extractRelations(code) {
|
|
9
|
+
return code
|
|
10
|
+
.map((line) => parseRelationLine(line))
|
|
11
|
+
.filter((relation) => relation !== null);
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Relation, TableInfo } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate ER content.
|
|
4
|
+
*
|
|
5
|
+
* @param relations - The relations to generate the ER content from.
|
|
6
|
+
* @param tables - The tables to generate the ER content from.
|
|
7
|
+
* @returns The generated ER content.
|
|
8
|
+
*/
|
|
9
|
+
export declare function erContent(relations: Relation[], tables: TableInfo[]): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { relationLine } from './index.js';
|
|
2
|
+
const ER_HEADER = ['```mermaid', 'erDiagram'];
|
|
3
|
+
const ER_FOOTER = ['```'];
|
|
4
|
+
/**
|
|
5
|
+
* Generate ER content.
|
|
6
|
+
*
|
|
7
|
+
* @param relations - The relations to generate the ER content from.
|
|
8
|
+
* @param tables - The tables to generate the ER content from.
|
|
9
|
+
* @returns The generated ER content.
|
|
10
|
+
*/
|
|
11
|
+
export function erContent(relations, tables) {
|
|
12
|
+
const relationLines = relations.map(relationLine);
|
|
13
|
+
const tableDefinitions = tables.flatMap((table) => [
|
|
14
|
+
` ${table.name} {`,
|
|
15
|
+
...table.fields.map((field) => ` ${field.type} ${field.name} ${field.description ? `"${field.description}"` : ''}`),
|
|
16
|
+
' }',
|
|
17
|
+
]);
|
|
18
|
+
const erContent = [
|
|
19
|
+
...ER_HEADER,
|
|
20
|
+
...relationLines,
|
|
21
|
+
...tableDefinitions,
|
|
22
|
+
...ER_FOOTER,
|
|
23
|
+
];
|
|
24
|
+
return erContent.join('\n');
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Relation } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a relation line for a relation.
|
|
4
|
+
*
|
|
5
|
+
* @param relation - The relation to generate a line for.
|
|
6
|
+
* @returns The generated relation line.
|
|
7
|
+
*/
|
|
8
|
+
export declare function relationLine(relation: Relation): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { buildRelationLine } from '../relationship/build-relation-line.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a relation line for a relation.
|
|
4
|
+
*
|
|
5
|
+
* @param relation - The relation to generate a line for.
|
|
6
|
+
* @returns The generated relation line.
|
|
7
|
+
*/
|
|
8
|
+
export function relationLine(relation) {
|
|
9
|
+
const cardinality = buildRelationLine(relation.type);
|
|
10
|
+
if (!cardinality) {
|
|
11
|
+
throw new Error(`Unknown relation type: ${relation.type}`);
|
|
12
|
+
}
|
|
13
|
+
return ` ${relation.fromModel} ${cardinality} ${relation.toModel} : "(${relation.fromField}) - (${relation.toField})"`;
|
|
14
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Mermaid ER diagram
|
|
3
|
+
* @param code - The code to generate Mermaid ER diagram from
|
|
4
|
+
* @param output - The output file path
|
|
5
|
+
*/
|
|
6
|
+
export declare function sizukuMermaidER(code: string[], output: string): Promise<import("neverthrow").Result<void, Error>>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { mkdir, writeFile } from '../../shared/fsp/index.js';
|
|
3
|
+
import { extractRelations } from './core/extract-relations.js';
|
|
4
|
+
import { erContent } from './generator/index.js';
|
|
5
|
+
import { parseTableInfo } from './validator/parse-table-info.js';
|
|
6
|
+
/**
|
|
7
|
+
* Generate Mermaid ER diagram
|
|
8
|
+
* @param code - The code to generate Mermaid ER diagram from
|
|
9
|
+
* @param output - The output file path
|
|
10
|
+
*/
|
|
11
|
+
export async function sizukuMermaidER(code, output) {
|
|
12
|
+
const tables = parseTableInfo(code);
|
|
13
|
+
const relations = extractRelations(code);
|
|
14
|
+
const ERContent = erContent(relations, tables);
|
|
15
|
+
return await mkdir(path.dirname(output)).andThen(() => writeFile(output, ERContent));
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const RELATIONSHIPS: {
|
|
2
|
+
readonly 'zero-one': "|o";
|
|
3
|
+
readonly one: "||";
|
|
4
|
+
readonly 'zero-many': "}o";
|
|
5
|
+
readonly many: "}|";
|
|
6
|
+
};
|
|
7
|
+
export type Relationship = keyof typeof RELATIONSHIPS;
|
|
8
|
+
/**
|
|
9
|
+
* Builds a relationship line for mermaid from a string.
|
|
10
|
+
*
|
|
11
|
+
* @param input - The relationship string to parse.
|
|
12
|
+
* @returns The mermaid relationship line.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildRelationLine(input: string): string;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isRelationship } from '../validator/is-relationship.js';
|
|
2
|
+
const RELATIONSHIPS = {
|
|
3
|
+
'zero-one': '|o',
|
|
4
|
+
one: '||',
|
|
5
|
+
'zero-many': '}o',
|
|
6
|
+
many: '}|',
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Builds a relationship line for mermaid from a string.
|
|
10
|
+
*
|
|
11
|
+
* @param input - The relationship string to parse.
|
|
12
|
+
* @returns The mermaid relationship line.
|
|
13
|
+
*/
|
|
14
|
+
export function buildRelationLine(input) {
|
|
15
|
+
const parts = input.split('-to-');
|
|
16
|
+
if (parts.length !== 2) {
|
|
17
|
+
throw new Error(`Invalid input format: ${input}`);
|
|
18
|
+
}
|
|
19
|
+
const [toRaw, optionalFlag] = parts[1].includes('-optional')
|
|
20
|
+
? [parts[1].replace('-optional', ''), 'optional']
|
|
21
|
+
: [parts[1], ''];
|
|
22
|
+
const from = parts[0];
|
|
23
|
+
const to = toRaw;
|
|
24
|
+
const isOptional = optionalFlag === 'optional';
|
|
25
|
+
if (!(isRelationship(from) && isRelationship(to))) {
|
|
26
|
+
throw new Error(`Invalid relationship string: ${input}`);
|
|
27
|
+
}
|
|
28
|
+
const fromSymbol = RELATIONSHIPS[from];
|
|
29
|
+
const toSymbol = RELATIONSHIPS[to];
|
|
30
|
+
if (!(fromSymbol && toSymbol)) {
|
|
31
|
+
throw new Error(`Invalid relationship string: ${input}`);
|
|
32
|
+
}
|
|
33
|
+
const connector = isOptional ? '..' : '--';
|
|
34
|
+
return `${fromSymbol}${connector}${toSymbol}`;
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Relation = {
|
|
2
|
+
fromModel: string;
|
|
3
|
+
toModel: string;
|
|
4
|
+
fromField: string;
|
|
5
|
+
toField: string;
|
|
6
|
+
type: string;
|
|
7
|
+
};
|
|
8
|
+
export type ERContent = readonly string[];
|
|
9
|
+
export type TableInfo = {
|
|
10
|
+
name: string;
|
|
11
|
+
fields: {
|
|
12
|
+
type: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description: string | null;
|
|
15
|
+
}[];
|
|
16
|
+
};
|
|
17
|
+
export type AccumulatorType = {
|
|
18
|
+
tables: TableInfo[];
|
|
19
|
+
currentTable: TableInfo | null;
|
|
20
|
+
currentDescription: string;
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Relationship } from '../relationship/build-relation-line.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check if a key is a valid relationship.
|
|
4
|
+
*
|
|
5
|
+
* @param key - The key to check.
|
|
6
|
+
* @returns True if the key is a valid relationship.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isRelationship(key: string): key is Relationship;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a relation line.
|
|
3
|
+
*
|
|
4
|
+
* @param line - The line to parse.
|
|
5
|
+
* @returns The parsed relation or null.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseRelationLine(line: string): {
|
|
8
|
+
fromModel: string;
|
|
9
|
+
fromField: string;
|
|
10
|
+
toModel: string;
|
|
11
|
+
toField: string;
|
|
12
|
+
type: string;
|
|
13
|
+
} | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a relation line.
|
|
3
|
+
*
|
|
4
|
+
* @param line - The line to parse.
|
|
5
|
+
* @returns The parsed relation or null.
|
|
6
|
+
*/
|
|
7
|
+
export function parseRelationLine(line) {
|
|
8
|
+
// @relation <fromModel>.<fromField> <toModel>.<toField> <relationType>
|
|
9
|
+
const relationMatch = line.match(/@relation\s+(\w+)\.(\w+)\s+(\w+)\.(\w+)\s+(\w+-to-\w+)/);
|
|
10
|
+
if (relationMatch) {
|
|
11
|
+
const [_, fromModel, fromField, toModel, toField, type] = relationMatch;
|
|
12
|
+
return {
|
|
13
|
+
fromModel,
|
|
14
|
+
fromField,
|
|
15
|
+
toModel,
|
|
16
|
+
toField,
|
|
17
|
+
type,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Node, Project } from 'ts-morph';
|
|
2
|
+
/**
|
|
3
|
+
* Get the base builder name from an expression.
|
|
4
|
+
*
|
|
5
|
+
* @param expr - The expression to get the builder name from.
|
|
6
|
+
* @returns The base builder name.
|
|
7
|
+
*/
|
|
8
|
+
function baseBuilderName(expr) {
|
|
9
|
+
if (Node.isIdentifier(expr))
|
|
10
|
+
return expr.getText();
|
|
11
|
+
if (Node.isCallExpression(expr) || Node.isPropertyAccessExpression(expr))
|
|
12
|
+
return baseBuilderName(expr.getExpression());
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Type guard for FieldInfo.
|
|
17
|
+
*
|
|
18
|
+
* @param v - The value to check.
|
|
19
|
+
* @returns True if the value is a FieldInfo.
|
|
20
|
+
*/
|
|
21
|
+
function isFieldInfo(v) {
|
|
22
|
+
return v !== null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parse table information from code.
|
|
26
|
+
*
|
|
27
|
+
* @param code - The code to parse.
|
|
28
|
+
* @returns Array of table information.
|
|
29
|
+
*/
|
|
30
|
+
export function parseTableInfo(code) {
|
|
31
|
+
const source = code.join('\n');
|
|
32
|
+
const file = new Project({ useInMemoryFileSystem: true }).createSourceFile('temp.ts', source);
|
|
33
|
+
return file
|
|
34
|
+
.getVariableStatements()
|
|
35
|
+
.filter((stmt) => stmt.isExported())
|
|
36
|
+
.flatMap((stmt) => {
|
|
37
|
+
const decl = stmt.getDeclarations()[0];
|
|
38
|
+
if (!Node.isVariableDeclaration(decl))
|
|
39
|
+
return [];
|
|
40
|
+
const varName = decl.getName();
|
|
41
|
+
if (varName.toLowerCase().includes('relation'))
|
|
42
|
+
return [];
|
|
43
|
+
const init = decl.getInitializer();
|
|
44
|
+
if (!(init && Node.isCallExpression(init)))
|
|
45
|
+
return [];
|
|
46
|
+
const callee = init.getExpression().getText();
|
|
47
|
+
if (!callee.endsWith('Table') || callee === 'relations')
|
|
48
|
+
return [];
|
|
49
|
+
const objLit = init.getArguments()[1];
|
|
50
|
+
if (!(objLit && Node.isObjectLiteralExpression(objLit)))
|
|
51
|
+
return [];
|
|
52
|
+
const fields = objLit
|
|
53
|
+
.getProperties()
|
|
54
|
+
.filter(Node.isPropertyAssignment)
|
|
55
|
+
.map((prop) => {
|
|
56
|
+
const keyNode = prop.getNameNode();
|
|
57
|
+
if (!Node.isIdentifier(keyNode))
|
|
58
|
+
return null;
|
|
59
|
+
const fieldName = keyNode.getText();
|
|
60
|
+
const initExpr = prop.getInitializer();
|
|
61
|
+
if (!(initExpr && Node.isCallExpression(initExpr)))
|
|
62
|
+
return null;
|
|
63
|
+
const fieldType = baseBuilderName(initExpr);
|
|
64
|
+
const initText = initExpr.getText();
|
|
65
|
+
const lineIdx = prop.getStartLineNumber() - 1;
|
|
66
|
+
const baseDesc = code
|
|
67
|
+
.slice(0, lineIdx)
|
|
68
|
+
.reverse()
|
|
69
|
+
.find((line) => {
|
|
70
|
+
const t = line.trim();
|
|
71
|
+
return (t.startsWith('///') &&
|
|
72
|
+
!t.includes('@z.') &&
|
|
73
|
+
!t.includes('@v.') &&
|
|
74
|
+
!t.includes('@relation'));
|
|
75
|
+
})
|
|
76
|
+
?.replace(/^\s*\/\/\/\s*/, '') ?? '';
|
|
77
|
+
const prefix = initText.includes('.primaryKey()')
|
|
78
|
+
? '(PK) '
|
|
79
|
+
: initText.includes('.references(')
|
|
80
|
+
? '(FK) '
|
|
81
|
+
: '';
|
|
82
|
+
return {
|
|
83
|
+
name: fieldName,
|
|
84
|
+
type: fieldType,
|
|
85
|
+
description: `${prefix}${baseDesc}`.trim(),
|
|
86
|
+
};
|
|
87
|
+
})
|
|
88
|
+
.filter(isFieldInfo);
|
|
89
|
+
return [{ name: varName, fields }];
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param schema
|
|
3
|
+
* @param comment
|
|
4
|
+
* @param type
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export declare function valibotCode(schema: {
|
|
8
|
+
name: string;
|
|
9
|
+
fields: {
|
|
10
|
+
name: string;
|
|
11
|
+
definition: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
}[];
|
|
14
|
+
}, comment: boolean, type: boolean): string;
|