sizuku 0.2.1 → 0.3.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 CHANGED
@@ -70,24 +70,30 @@ export const postRelations = relations(post, ({ one }) => ({
70
70
  }))
71
71
  ```
72
72
 
73
- Prepare sizuku.json:
74
-
75
- ```json
76
- {
77
- "input": "db/schema.ts",
78
- "zod": {
79
- "output": "zod/index.ts",
80
- "comment": true,
81
- "type": true
73
+ Prepare sizuku.config.ts:
74
+
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
+ relation: true,
82
86
  },
83
- "valibot": {
84
- "output": "valibot/index.ts",
85
- "comment": true
87
+ valibot: {
88
+ output: 'valibot/index.ts',
89
+ comment: true,
90
+ type: true,
91
+ relation: true,
86
92
  },
87
- "mermaid": {
88
- "output": "mermaid-er/ER.md"
89
- }
90
- }
93
+ mermaid: {
94
+ output: 'mermaid-er/ER.md',
95
+ },
96
+ })
91
97
  ```
92
98
 
93
99
  Run Sizuku:
@@ -160,6 +166,8 @@ export const UserSchema = v.object({
160
166
  name: v.pipe(v.string(), v.minLength(1), v.maxLength(50)),
161
167
  })
162
168
 
169
+ export type User = v.InferInput<typeof UserSchema>
170
+
163
171
  export const PostSchema = v.object({
164
172
  /**
165
173
  * Primary key
@@ -179,9 +187,15 @@ export const PostSchema = v.object({
179
187
  userId: v.pipe(v.string(), v.uuid()),
180
188
  })
181
189
 
190
+ export type Post = v.InferInput<typeof PostSchema>
191
+
182
192
  export const UserRelationsSchema = v.object({ ...UserSchema.entries, posts: v.array(PostSchema) })
183
193
 
194
+ export type UserRelations = v.InferInput<typeof UserRelationsSchema>
195
+
184
196
  export const PostRelationsSchema = v.object({ ...PostSchema.entries, user: UserSchema })
197
+
198
+ export type PostRelations = v.InferInput<typeof PostRelationsSchema>
185
199
  ```
186
200
 
187
201
  ### Mermaid ER
@@ -1,18 +1,27 @@
1
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';
2
+ readonly input: `${string}.ts`;
3
+ readonly zod?: {
4
+ readonly output: `${string}.ts`;
5
+ readonly comment?: boolean;
6
+ readonly type?: boolean;
7
+ readonly zod?: 'v4' | 'mini' | '@hono/zod-openapi';
8
+ readonly relation?: boolean;
8
9
  };
9
- valibot?: {
10
- output?: `${string}.ts`;
11
- comment?: boolean;
12
- type?: boolean;
10
+ readonly valibot?: {
11
+ readonly output: `${string}.ts`;
12
+ readonly comment?: boolean;
13
+ readonly type?: boolean;
14
+ readonly relation?: boolean;
13
15
  };
14
- mermaid?: {
15
- output?: string;
16
+ readonly mermaid?: {
17
+ readonly output: string;
16
18
  };
17
19
  };
18
- export declare function getConfig(): Config;
20
+ export declare function config(): Promise<{
21
+ readonly ok: true;
22
+ readonly value: Config;
23
+ } | {
24
+ readonly ok: false;
25
+ readonly error: string;
26
+ }>;
27
+ export default function defineConfig(config: Config): Config;
@@ -1,13 +1,85 @@
1
- import fs from 'node:fs';
2
- export function getConfig() {
3
- if (!fs.existsSync('sizuku.json')) {
4
- throw new Error('sizuku.json not found');
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
- 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
- };
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} ${field.description ? `"${field.description}"` : ''}`),
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
- const baseDesc = code
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: `${prefix}${baseDesc}`.trim(),
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, relations?: boolean): Promise<{
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, relations) {
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
- ...(relations
21
+ ...(relation
22
22
  ? relationSchemas.map((schema) => relationValibotCode(schema, type ?? false))
23
23
  : []),
24
24
  ].join('\n');
@@ -6,10 +6,10 @@
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', relations?: boolean): Promise<{
10
- ok: true;
11
- value: undefined;
9
+ export declare function sizukuZod(code: string[], output: `${string}.ts`, comment?: boolean, type?: boolean, zod?: 'v4' | 'mini' | '@hono/zod-openapi', relation?: boolean): Promise<{
10
+ readonly ok: true;
11
+ readonly value: undefined;
12
12
  } | {
13
- ok: false;
14
- error: string;
13
+ readonly ok: false;
14
+ readonly error: string;
15
15
  }>;
@@ -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, relations) {
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
- ...(relations ? relationSchemas.map((schema) => relationZodCode(schema, type ?? false)) : []),
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
@@ -1,8 +1,7 @@
1
- import type { Config } from './config/index.js';
2
- export declare function main(config?: Config): Promise<{
3
- ok: true;
4
- value: string;
1
+ export declare function main(): Promise<{
2
+ readonly ok: true;
3
+ readonly value: string;
5
4
  } | {
6
- ok: false;
7
- error: string;
5
+ readonly ok: false;
6
+ readonly error: string;
8
7
  }>;
package/dist/index.js CHANGED
@@ -1,17 +1,19 @@
1
1
  // #!/usr/bin/env node
2
- import { getConfig } from './config/index.js';
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(config = getConfig()) {
8
- if (!config.input) {
7
+ export async function main() {
8
+ const configResult = await config();
9
+ if (!configResult.ok) {
9
10
  return {
10
11
  ok: false,
11
- error: 'input is not found',
12
+ error: configResult.error,
12
13
  };
13
14
  }
14
- const contentResult = readFileSync(config.input);
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 (config.zod?.output) {
28
- const zodResult = await sizukuZod(code, config.zod.output, config.zod.comment, config.zod.type, config.zod.zod, true);
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: ${config.zod?.output}`);
37
+ results.push(`Generated Zod schema at: ${c.zod?.output}`);
36
38
  }
37
39
  /* valibot */
38
- if (config.valibot?.output) {
39
- const valibotResult = await sizukuValibot(code, config.valibot.output, config.valibot.comment, config.valibot.type, true);
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: ${config.valibot?.output}`);
48
+ results.push(`Generated Valibot schema at: ${c.valibot?.output}`);
47
49
  }
48
50
  /* mermaid */
49
- if (config.mermaid?.output) {
50
- const mermaidResult = await sizukuMermaidER(code, config.mermaid.output);
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: ${config.mermaid?.output}`);
59
+ results.push(`Generated Mermaid ER at: ${c.mermaid?.output}`);
58
60
  }
59
61
  return {
60
62
  ok: true,
@@ -5,11 +5,9 @@
5
5
  * @returns A `Result` containing the formatted code or an error message.
6
6
  */
7
7
  export declare function fmt(code: string): Promise<{
8
- ok: true;
9
- value: string;
10
- error?: undefined;
8
+ readonly ok: true;
9
+ readonly value: string;
11
10
  } | {
12
- ok: false;
13
- error: string;
14
- value?: undefined;
11
+ readonly ok: false;
12
+ readonly error: string;
15
13
  }>;
@@ -7,13 +7,13 @@ import { format } from 'prettier';
7
7
  */
8
8
  export async function fmt(code) {
9
9
  try {
10
- const formatted = await format(code, {
10
+ const result = await format(code, {
11
11
  parser: 'typescript',
12
12
  printWidth: 100,
13
13
  singleQuote: true,
14
14
  semi: false,
15
15
  });
16
- return { ok: true, value: formatted };
16
+ return { ok: true, value: result };
17
17
  }
18
18
  catch (e) {
19
19
  return {
@@ -1,7 +1,7 @@
1
1
  export declare function readFileSync(path: string): {
2
- ok: true;
3
- value: string;
2
+ readonly ok: true;
3
+ readonly value: string;
4
4
  } | {
5
- ok: false;
6
- error: string;
5
+ readonly ok: false;
6
+ readonly error: string;
7
7
  };
@@ -5,11 +5,11 @@
5
5
  * @returns A `Result` that is `ok` on success, otherwise an error message.
6
6
  */
7
7
  export declare function mkdir(dir: string): Promise<{
8
- ok: false;
9
- error: string;
8
+ readonly ok: false;
9
+ readonly error: string;
10
10
  } | {
11
- ok: true;
12
- value: undefined;
11
+ readonly ok: true;
12
+ readonly value: undefined;
13
13
  }>;
14
14
  /**
15
15
  * Writes UTF-8 text to a file, creating it if necessary.
@@ -48,14 +48,14 @@ export declare function removeAtSign(str: string): string;
48
48
  * @param arr - Array of strings to join.
49
49
  * @returns Joined string with spaces.
50
50
  */
51
- export declare function joinWithSpace(arr: string[]): string;
51
+ export declare function joinWithSpace(arr: readonly string[]): string;
52
52
  /**
53
53
  * Split string by newline character.
54
54
  *
55
55
  * @param str - The input string.
56
56
  * @returns Array of strings split by newline.
57
57
  */
58
- export declare function splitByNewline(str: string): string[];
58
+ export declare function splitByNewline(str: string): readonly string[];
59
59
  /**
60
60
  * Trim whitespace from string.
61
61
  *
@@ -96,14 +96,14 @@ export declare function removeOptionalSuffix(str: string): string;
96
96
  * @param str - The input string.
97
97
  * @returns Array of strings split by whitespace.
98
98
  */
99
- export declare function splitByWhitespace(str: string): string[];
99
+ export declare function splitByWhitespace(str: string): readonly string[];
100
100
  /**
101
101
  * Split string by dot character.
102
102
  *
103
103
  * @param str - The input string.
104
104
  * @returns Array of strings split by dot.
105
105
  */
106
- export declare function splitByDot(str: string): string[];
106
+ export declare function splitByDot(str: string): readonly string[];
107
107
  /**
108
108
  * Parse field comments and extract definition line and description.
109
109
  *
@@ -135,10 +135,10 @@ export declare function inferInput(name: string): string;
135
135
  * @returns
136
136
  */
137
137
  export declare function fieldDefinitions(schema: {
138
- name: string;
139
- fields: {
140
- name: string;
141
- definition: string;
142
- description?: string;
138
+ readonly name: string;
139
+ readonly fields: {
140
+ readonly name: string;
141
+ readonly definition: string;
142
+ readonly description?: string;
143
143
  }[];
144
144
  }, comment: boolean): string;
@@ -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
- const result = [];
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
- let start = 0;
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(line, '@relation'))
94
+ if (!line.startsWith('@relation'))
117
95
  return null;
118
- const parts = splitByWhitespace(line);
96
+ const parts = line.trim().split(/\s+/);
119
97
  if (parts.length < 5)
120
98
  return null;
121
- const fromParts = splitByDot(parts[1]);
122
- const toParts = splitByDot(parts[2]);
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
- const result = [];
163
- let current = '';
164
- let inWord = false;
165
- for (let i = 0; i < str.length; i++) {
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
- const result = [];
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.map((line) => removeTripleSlash(line).trim()).filter(isNonEmpty);
218
- // Extract object type from strictObject/looseObject tags
219
- const objectTypeLine = cleaned.find((line) => containsSubstring(line, `${tag.slice(1)}strictObject`) ||
220
- containsSubstring(line, `${tag.slice(1)}looseObject`));
221
- const objectType = objectTypeLine && containsSubstring(objectTypeLine, 'strictObject')
222
- ? 'strict'
223
- : objectTypeLine && containsSubstring(objectTypeLine, 'looseObject')
224
- ? 'loose'
225
- : undefined;
226
- // Extract definition (excluding strictObject/looseObject tags)
227
- const definitionLine = cleaned.find((line) => startsWith(line, tag) &&
228
- !containsSubstring(line, 'strictObject') &&
229
- !containsSubstring(line, 'looseObject'));
230
- const definition = definitionLine ? removeAtSign(definitionLine) : '';
231
- const descriptionLines = cleaned.filter((line) => !(containsSubstring(line, '@z.') ||
232
- containsSubstring(line, '@v.') ||
233
- containsSubstring(line, '@relation.')));
234
- const description = descriptionLines.length > 0 ? joinWithSpace(descriptionLines) : undefined;
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 = splitByNewline(beforeField);
198
+ const lines = beforeField.split('\n');
250
199
  const reverseIndex = lines
251
- .map((line, index) => ({ line: trimString(line), index }))
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(line, '///')) {
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.2.1",
4
+ "version": "0.3.1",
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
  }