hekireki 0.5.0 → 0.5.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
@@ -373,84 +373,77 @@ Output: `docs/er-diagram.png`
373
373
 
374
374
  ## Configuration
375
375
 
376
- ### Zod Generator Options
377
-
378
- | Option | Type | Default | Description |
379
- |--------------|-----------|-------------------------------------|--------------------------------------------------|
380
- | `output` | `string` | `./zod` | Output directory |
381
- | `file` | `string` | `index.ts` | File Name |
382
- | `type` | `boolean` | `false` | Generate TypeScript types |
383
- | `comment` | `boolean` | `false` | Include schema documentation |
384
- | `zod` | `string` | `'v4'` | Zod import version (`'mini'`, `'@hono/zod-openapi'`, or default `'v4'`) |
385
- | `relation` | `boolean` | `false` | Generate relation schemas |
386
-
387
- ### Valibot Generator Options
388
-
389
- | Option | Type | Default | Description |
390
- |--------------|-----------|-------------------------------------|--------------------------------------------------|
391
- | `output` | `string` | `./valibot` | Output directory |
392
- | `file` | `string` | `index.ts` | File Name |
393
- | `type` | `boolean` | `false` | Generate TypeScript types |
394
- | `comment` | `boolean` | `false` | Include schema documentation |
395
- | `relation` | `boolean` | `false` | Generate relation schemas |
396
-
397
- ### ArkType Generator Options
398
-
399
- | Option | Type | Default | Description |
400
- |--------------|-----------|-------------------------------------|--------------------------------------------------|
401
- | `output` | `string` | `./arktype` | Output directory |
402
- | `file` | `string` | `index.ts` | File Name |
403
- | `type` | `boolean` | `false` | Generate TypeScript types |
404
- | `comment` | `boolean` | `false` | Include schema documentation |
405
-
406
- ### Effect Schema Generator Options
407
-
408
- | Option | Type | Default | Description |
409
- |--------------|-----------|-------------------------------------|--------------------------------------------------|
410
- | `output` | `string` | `./effect` | Output directory |
411
- | `file` | `string` | `index.ts` | File Name |
412
- | `type` | `boolean` | `false` | Generate TypeScript types |
413
- | `comment` | `boolean` | `false` | Include schema documentation |
414
-
415
- ### Mermaid ER Generator Options
416
-
417
- | Option | Type | Default | Description |
418
- |--------------|-----------|-------------------------------------|--------------------------------------------------|
419
- | `output` | `string` | `./mermaid-er` | Output directory |
420
- | `file` | `string` | `ER.md` | File Name |
421
-
422
- ### Ecto Generator Options
423
-
424
- | Option | Type | Default | Description |
425
- |--------------|-----------|-------------------------------------|--------------------------------------------------|
426
- | `output` | `string` | `./ecto` | Output directory |
427
- | `app` | `string` | `MyApp` | App Name |
428
-
429
- ### DBML Generator Options
430
-
431
- | Option | Type | Default | Description |
432
- |--------------|-----------|-------------------------------------|--------------------------------------------------|
433
- | `output` | `string` | `./dbml` | Output directory |
434
- | `file` | `string` | `schema.dbml` | File Name |
435
-
436
- ### SVG Generator Options
437
-
438
- | Option | Type | Default | Description |
439
- |--------------|-----------|-------------------------------------|--------------------------------------------------|
440
- | `output` | `string` | `./docs` | Output directory |
441
- | `file` | `string` | `er-diagram` | File Name (without extension) |
442
- | `format` | `string` | `png` | Output format (`png`, `svg`, or `dot`) |
443
-
444
- ## Annotation Prefixes
445
-
446
- Each generator uses a specific annotation prefix in Prisma schema comments:
447
-
448
- | Generator | Prefix | Example |
449
- |----------------|--------|------------------------------------------------------------|
450
- | Zod | `@z.` | `/// @z.uuid()` |
451
- | Valibot | `@v.` | `/// @v.pipe(v.string(), v.uuid())` |
452
- | ArkType | `@a.` | `/// @a."string.uuid"` |
453
- | Effect Schema | `@e.` | `/// @e.Schema.UUID` |
376
+ Configure each generator directly in your `schema.prisma` file:
377
+
378
+ ```prisma
379
+ // Zod Generator
380
+ generator Hekireki-Zod {
381
+ provider = "hekireki-zod"
382
+ output = "./zod" // Output directory (default: ./zod)
383
+ file = "index.ts" // File name (default: index.ts)
384
+ type = true // Generate TypeScript types (default: false)
385
+ comment = true // Include schema documentation (default: false)
386
+ zod = "v4" // Zod import: "v4", "mini", or "@hono/zod-openapi" (default: v4)
387
+ relation = true // Generate relation schemas (default: false)
388
+ }
389
+
390
+ // Valibot Generator
391
+ generator Hekireki-Valibot {
392
+ provider = "hekireki-valibot"
393
+ output = "./valibot" // Output directory (default: ./valibot)
394
+ file = "index.ts" // File name (default: index.ts)
395
+ type = true // Generate TypeScript types (default: false)
396
+ comment = true // Include schema documentation (default: false)
397
+ relation = true // Generate relation schemas (default: false)
398
+ }
399
+
400
+ // ArkType Generator
401
+ generator Hekireki-ArkType {
402
+ provider = "hekireki-arktype"
403
+ output = "./arktype" // Output directory (default: ./arktype)
404
+ file = "index.ts" // File name (default: index.ts)
405
+ type = true // Generate TypeScript types (default: false)
406
+ comment = true // Include schema documentation (default: false)
407
+ }
408
+
409
+ // Effect Schema Generator
410
+ generator Hekireki-Effect {
411
+ provider = "hekireki-effect"
412
+ output = "./effect" // Output directory (default: ./effect)
413
+ file = "index.ts" // File name (default: index.ts)
414
+ type = true // Generate TypeScript types (default: false)
415
+ comment = true // Include schema documentation (default: false)
416
+ }
417
+
418
+ // Mermaid ER Generator
419
+ generator Hekireki-ER {
420
+ provider = "hekireki-mermaid-er"
421
+ output = "./mermaid-er" // Output directory (default: ./mermaid-er)
422
+ file = "ER.md" // File name (default: ER.md)
423
+ }
424
+
425
+ // Ecto Generator
426
+ generator Hekireki-Ecto {
427
+ provider = "hekireki-ecto"
428
+ output = "./ecto" // Output directory (default: ./ecto)
429
+ app = "MyApp" // App name (default: MyApp)
430
+ }
431
+
432
+ // DBML Generator
433
+ generator Hekireki-DBML {
434
+ provider = "hekireki-dbml"
435
+ output = "./dbml" // Output directory (default: ./dbml)
436
+ file = "schema.dbml" // File name (default: schema.dbml)
437
+ }
438
+
439
+ // SVG/PNG Generator
440
+ generator Hekireki-SVG {
441
+ provider = "hekireki-svg"
442
+ output = "./docs" // Output directory (default: ./docs)
443
+ file = "er-diagram" // File name without extension (default: er-diagram)
444
+ format = "png" // Output format: "png", "svg", or "dot" (default: png)
445
+ }
446
+ ```
454
447
 
455
448
  ## License
456
449
 
@@ -0,0 +1,14 @@
1
+ //#region src/cli/index.d.ts
2
+ type Result<T> = {
3
+ readonly ok: true;
4
+ readonly value: T;
5
+ } | {
6
+ readonly ok: false;
7
+ readonly error: string;
8
+ };
9
+ /**
10
+ * Main CLI entry point for hekireki.
11
+ */
12
+ declare const hekireki: () => Result<string>;
13
+ //#endregion
14
+ export { hekireki };
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { serve } from "@hono/node-server";
5
+ import { serveStatic } from "@hono/node-server/serve-static";
6
+ import { Hono } from "hono";
7
+
8
+ //#region src/cli/index.ts
9
+ /**
10
+ * CLI module for hekireki.
11
+ *
12
+ * Provides the main entry point for the hekireki CLI tool.
13
+ *
14
+ * @module cli
15
+ */
16
+ const HELP_TEXT = `⚡️ hekireki - Prisma schema tools
17
+
18
+ Usage:
19
+ hekireki <command> [options]
20
+
21
+ Commands:
22
+ docs serve Start a local server to view the documentation
23
+
24
+ Options:
25
+ -p, --port <port> Specify the port (default: 5858)
26
+ -h, --help Show help
27
+
28
+ Examples:
29
+ hekireki docs serve
30
+ hekireki docs serve -p 3000`;
31
+ const DOCS_HELP_TEXT = `⚡️ hekireki docs - Documentation tools
32
+
33
+ Usage:
34
+ hekireki docs serve [options]
35
+
36
+ Commands:
37
+ serve Start a local server to view the documentation
38
+
39
+ Options:
40
+ -p, --port <port> Specify the port (default: 5858)
41
+ -h, --help Show help
42
+
43
+ Examples:
44
+ hekireki docs serve
45
+ hekireki docs serve -p 3000`;
46
+ /**
47
+ * Parse port from CLI arguments.
48
+ */
49
+ const parsePort = (args) => {
50
+ const portIndex = args.findIndex((arg) => arg === "-p" || arg === "--port");
51
+ if (portIndex === -1) return {
52
+ ok: true,
53
+ value: 5858
54
+ };
55
+ const portStr = args[portIndex + 1];
56
+ if (!portStr || portStr.startsWith("-")) return {
57
+ ok: false,
58
+ error: "❌ Error: --port requires a number"
59
+ };
60
+ const port = parseInt(portStr, 10);
61
+ if (isNaN(port)) return {
62
+ ok: false,
63
+ error: `❌ Error: Invalid port number: ${portStr}`
64
+ };
65
+ return {
66
+ ok: true,
67
+ value: port
68
+ };
69
+ };
70
+ /**
71
+ * Parse CLI arguments for docs serve command.
72
+ */
73
+ const parseDocsServeArgs = (args) => {
74
+ const portResult = parsePort(args);
75
+ if (!portResult.ok) return portResult;
76
+ return {
77
+ ok: true,
78
+ value: { port: portResult.value }
79
+ };
80
+ };
81
+ /**
82
+ * Start the documentation server.
83
+ */
84
+ const startDocsServer = (options) => {
85
+ const absolutePath = path.resolve("./docs");
86
+ if (!fs.existsSync(absolutePath)) return {
87
+ ok: false,
88
+ error: `❌ Error: Directory not found: ${absolutePath}\n Run "prisma generate" first to generate the documentation.`
89
+ };
90
+ const indexPath = path.join(absolutePath, "index.html");
91
+ if (!fs.existsSync(indexPath)) return {
92
+ ok: false,
93
+ error: `❌ Error: index.html not found in ${absolutePath}\n Run "prisma generate" first to generate the documentation.`
94
+ };
95
+ const app = new Hono();
96
+ app.get("/", (c) => {
97
+ const html = fs.readFileSync(indexPath, "utf-8");
98
+ return c.html(html);
99
+ });
100
+ app.use("/*", serveStatic({ root: absolutePath }));
101
+ const server = serve({
102
+ fetch: app.fetch,
103
+ port: options.port
104
+ });
105
+ process.on("SIGTERM", () => {
106
+ server.close();
107
+ process.exit(0);
108
+ });
109
+ process.on("SIGINT", () => {
110
+ server.close();
111
+ process.exit(0);
112
+ });
113
+ return {
114
+ ok: true,
115
+ value: `⚡️ Hekireki Docs Server started at http://localhost:${options.port}\n📂 Serving documentation from: ${absolutePath}`
116
+ };
117
+ };
118
+ /**
119
+ * Handle docs subcommand.
120
+ */
121
+ const handleDocs = (args) => {
122
+ const subcommand = args[0];
123
+ if (!subcommand || subcommand === "-h" || subcommand === "--help") return {
124
+ ok: true,
125
+ value: DOCS_HELP_TEXT
126
+ };
127
+ if (subcommand !== "serve") return {
128
+ ok: false,
129
+ error: `❌ Unknown command: docs ${subcommand}\n\n${DOCS_HELP_TEXT}`
130
+ };
131
+ const parseResult = parseDocsServeArgs(args.slice(1));
132
+ if (!parseResult.ok) return parseResult;
133
+ return startDocsServer(parseResult.value);
134
+ };
135
+ /**
136
+ * Command handlers map.
137
+ */
138
+ const commands = { docs: handleDocs };
139
+ /**
140
+ * Main CLI entry point for hekireki.
141
+ */
142
+ const hekireki = () => {
143
+ const args = process.argv.slice(2);
144
+ const command = args[0];
145
+ if (!command || command === "-h" || command === "--help") return {
146
+ ok: true,
147
+ value: HELP_TEXT
148
+ };
149
+ const handler = commands[command];
150
+ if (!handler) return {
151
+ ok: false,
152
+ error: `❌ Unknown command: ${command}\n\n${HELP_TEXT}`
153
+ };
154
+ return handler(args.slice(1));
155
+ };
156
+ const result = hekireki();
157
+ if (result.ok) console.log(result.value);
158
+ else {
159
+ console.error(result.error);
160
+ process.exit(1);
161
+ }
162
+
163
+ //#endregion
164
+ export { hekireki };
@@ -1,4 +1,4 @@
1
- import fsp from "node:fs/promises";
1
+ import fs from "node:fs/promises";
2
2
 
3
3
  //#region src/shared/fsp/index.ts
4
4
  /**
@@ -9,7 +9,7 @@ import fsp from "node:fs/promises";
9
9
  */
10
10
  async function mkdir(dir) {
11
11
  try {
12
- await fsp.mkdir(dir, { recursive: true });
12
+ await fs.mkdir(dir, { recursive: true });
13
13
  return {
14
14
  ok: true,
15
15
  value: void 0
@@ -30,7 +30,7 @@ async function mkdir(dir) {
30
30
  */
31
31
  async function writeFile(path, data) {
32
32
  try {
33
- await fsp.writeFile(path, data, "utf-8");
33
+ await fs.writeFile(path, data, "utf-8");
34
34
  return {
35
35
  ok: true,
36
36
  value: void 0
@@ -51,7 +51,7 @@ async function writeFile(path, data) {
51
51
  */
52
52
  async function writeFileBinary(path, data) {
53
53
  try {
54
- await fsp.writeFile(path, data);
54
+ await fs.writeFile(path, data);
55
55
  return {
56
56
  ok: true,
57
57
  value: void 0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as getString, i as getBool, n as validationSchemas, o as fmt, r as parseDocumentWithoutAnnotations, t as schemaFromFields } from "../../utils-CXBzdZih.js";
3
- import { n as writeFile, t as mkdir } from "../../fsp-DYtOxLN_.js";
2
+ import { a as getBool, i as collectRelationProps, n as validationSchemas, o as getString, r as parseDocumentWithoutAnnotations, s as fmt, t as schemaFromFields } from "../../utils-BHGDO4qk.js";
3
+ import { n as writeFile, t as mkdir } from "../../fsp-FOfmtPYd.js";
4
4
  import path from "node:path";
5
5
  import pkg from "@prisma/generator-helper";
6
6
  import { makeValidationExtractor } from "utils-lab";
@@ -15,6 +15,19 @@ import { makeValidationExtractor } from "utils-lab";
15
15
  function schema(modelName, fields) {
16
16
  return `export const ${modelName}Schema = type({\n${fields}\n})`;
17
17
  }
18
+ /**
19
+ * Make ArkType relations schema for a model.
20
+ * @param model - The DMMF model
21
+ * @param relProps - The relation properties
22
+ * @param options - Options for the relation generation
23
+ * @returns The generated ArkType relations schema or null if no relations
24
+ */
25
+ function makeArktypeRelations(model, relProps, options) {
26
+ if (relProps.length === 0) return null;
27
+ const fields = `${`...${model.name}Schema.t,`}${relProps.map((r) => `${r.key}:${r.isMany ? `${r.targetModel}Schema.array()` : `${r.targetModel}Schema`},`).join("")}`;
28
+ const typeLine = options?.includeType ? `\n\nexport type ${model.name}Relations = typeof ${model.name}RelationsSchema.infer` : "";
29
+ return `export const ${model.name}RelationsSchema = type({${fields}})${typeLine}`;
30
+ }
18
31
 
19
32
  //#endregion
20
33
  //#region src/generator/arktype/generator/schemas.ts
@@ -69,17 +82,31 @@ function arktype(models, type, comment) {
69
82
  //#endregion
70
83
  //#region src/generator/arktype/index.ts
71
84
  const { generatorHandler } = pkg;
72
- const emit = async (options) => {
85
+ const buildRelationsOnly = (dmmf, includeType) => {
86
+ const models = dmmf.datamodel.models;
87
+ const relIndex = collectRelationProps(models);
88
+ const relByModel = {};
89
+ for (const r of relIndex) {
90
+ const existing = relByModel[r.model] ?? [];
91
+ relByModel[r.model] = [...existing, r];
92
+ }
93
+ return models.map((model) => makeArktypeRelations(model, (relByModel[model.name] ?? []).map(({ key, targetModel, isMany }) => ({
94
+ key,
95
+ targetModel,
96
+ isMany
97
+ })), { includeType })).filter((code) => Boolean(code)).join("\n\n");
98
+ };
99
+ const emit = async (options, enableRelation) => {
73
100
  const outDir = options.generator.output?.value ?? "./arktype";
74
101
  const file = getString(options.generator.config?.file, "index.ts") ?? "index.ts";
75
- const fmtResult = await fmt(arktype(options.dmmf.datamodel.models, getBool(options.generator.config?.type), getBool(options.generator.config?.comment)));
102
+ const fmtResult = await fmt([arktype(options.dmmf.datamodel.models, getBool(options.generator.config?.type), getBool(options.generator.config?.comment)), enableRelation ? buildRelationsOnly(options.dmmf, getBool(options.generator.config?.type)) : ""].filter(Boolean).join("\n\n"));
76
103
  if (!fmtResult.ok) throw new Error(`Format error: ${fmtResult.error}`);
77
104
  const mkdirResult = await mkdir(outDir);
78
105
  if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
79
106
  const writeResult = await writeFile(path.join(outDir, file), fmtResult.value);
80
107
  if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
81
108
  };
82
- const onGenerate = (options) => emit(options);
109
+ const onGenerate = (options) => emit(options, options.generator.config?.relation === "true" || Array.isArray(options.generator.config?.relation) && options.generator.config?.relation[0] === "true");
83
110
  generatorHandler({
84
111
  onManifest() {
85
112
  return {
@@ -1,6 +1,9 @@
1
1
  import { GeneratorOptions } from "@prisma/generator-helper";
2
2
 
3
3
  //#region src/generator/dbml/index.d.ts
4
- declare function main(options: GeneratorOptions): Promise<void>;
4
+ /**
5
+ * Main generator function
6
+ */
7
+ declare const main: (options: GeneratorOptions) => Promise<void>;
5
8
  //#endregion
6
9
  export { main };