hekireki 0.6.0 โ†’ 0.6.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
@@ -1,3 +1,5 @@
1
+ ![img](https://raw.githubusercontent.com/nakita628/hekireki/refs/heads/main/assets/img/hekireki.png)
2
+
1
3
  # Hekireki
2
4
 
3
5
  **[Hekireki](https://www.npmjs.com/package/hekireki)** is a tool that generates validation schemas for Zod, Valibot, ArkType, and Effect Schema, as well as ER diagrams, from [Prisma](https://www.prisma.io/) schemas annotated with comments.
@@ -9,8 +11,7 @@
9
11
  - ๐Ÿน Automatically generates [ArkType](https://arktype.io/) schemas from your Prisma schema
10
12
  - โšก Automatically generates [Effect Schema](https://effect.website/docs/schema/introduction/) from your Prisma schema
11
13
  - ๐Ÿ“Š Creates [Mermaid](https://mermaid.js.org/) ER diagrams with PK/FK markers
12
- - ๐Ÿ“ Generates [DBML](https://dbml.dbdiagram.io/) (Database Markup Language) files
13
- - ๐Ÿ–ผ๏ธ Outputs ER diagrams as **PNG/SVG** images using [dbml-renderer](https://github.com/softwaretechnik-berlin/dbml-renderer)
14
+ - ๐Ÿ“ Generates [DBML](https://dbml.dbdiagram.io/) (Database Markup Language) files and **PNG** ER diagrams via [dbml-renderer](https://github.com/softwaretechnik-berlin/dbml-renderer) โ€” output format is determined by the file extension (`.dbml` or `.png`)
14
15
  - ๐Ÿงช Generates [Ecto](https://hexdocs.pm/ecto/Ecto.Schema.html) schemas for Elixir projects
15
16
  โš ๏ธ Foreign key constraints are **not** included โ€” manage relationships in your application logic
16
17
 
@@ -51,28 +52,30 @@ generator Hekireki-ArkType {
51
52
  provider = "hekireki-arktype"
52
53
  type = true
53
54
  comment = true
55
+ relation = true
54
56
  }
55
57
 
56
58
  generator Hekireki-Effect {
57
59
  provider = "hekireki-effect"
58
60
  type = true
59
61
  comment = true
62
+ relation = true
60
63
  }
61
64
 
62
65
  generator Hekireki-Ecto {
63
66
  provider = "hekireki-ecto"
64
- output = "schema"
67
+ output = "./ecto"
65
68
  app = "DBSchema"
66
69
  }
67
70
 
68
71
  generator Hekireki-DBML {
69
72
  provider = "hekireki-dbml"
73
+ output = "docs/schema.dbml"
70
74
  }
71
75
 
72
- generator Hekireki-SVG {
73
- provider = "hekireki-svg"
74
- output = "docs"
75
- format = "png"
76
+ generator Hekireki-Docs {
77
+ provider = "hekireki-docs"
78
+ output = "./docs"
76
79
  }
77
80
 
78
81
  model User {
@@ -122,9 +125,9 @@ model Post {
122
125
  }
123
126
  ```
124
127
 
125
- ## Generate
128
+ ## Generated Output
126
129
 
127
- ## Zod
130
+ ### Zod
128
131
 
129
132
  ```ts
130
133
  import * as z from 'zod'
@@ -178,7 +181,7 @@ export const PostRelationsSchema = z.object({
178
181
  export type PostRelations = z.infer<typeof PostRelationsSchema>
179
182
  ```
180
183
 
181
- ## Valibot
184
+ ### Valibot
182
185
 
183
186
  ```ts
184
187
  import * as v from 'valibot'
@@ -232,7 +235,7 @@ export const PostRelationsSchema = v.object({
232
235
  export type PostRelations = v.InferInput<typeof PostRelationsSchema>
233
236
  ```
234
237
 
235
- ## ArkType
238
+ ### ArkType
236
239
 
237
240
  ```ts
238
241
  import { type } from 'arktype'
@@ -260,7 +263,7 @@ export const PostSchema = type({
260
263
  export type Post = typeof PostSchema.infer
261
264
  ```
262
265
 
263
- ## Effect Schema
266
+ ### Effect Schema
264
267
 
265
268
  ```ts
266
269
  import { Schema } from 'effect'
@@ -288,7 +291,7 @@ export const PostSchema = Schema.Struct({
288
291
  export type Post = Schema.Schema.Type<typeof PostSchema>
289
292
  ```
290
293
 
291
- ## Mermaid
294
+ ### Mermaid
292
295
 
293
296
  ```mermaid
294
297
  erDiagram
@@ -305,7 +308,7 @@ erDiagram
305
308
  }
306
309
  ```
307
310
 
308
- ## Ecto
311
+ ### Ecto
309
312
 
310
313
  ```elixir
311
314
  defmodule DBSchema.User do
@@ -345,7 +348,7 @@ defmodule DBSchema.Post do
345
348
  end
346
349
  ```
347
350
 
348
- ## DBML
351
+ ### DBML
349
352
 
350
353
  ```dbml
351
354
  Table User {
@@ -365,11 +368,27 @@ Table Post {
365
368
  Ref Post_userId_fk: Post.userId > User.id
366
369
  ```
367
370
 
368
- ## PNG/SVG
371
+ ### PNG
369
372
 
370
- The `hekireki-svg` generator outputs ER diagrams as PNG or SVG images using [dbml-renderer](https://github.com/softwaretechnik-berlin/dbml-renderer).
373
+ The `hekireki-dbml` generator also outputs ER diagrams as PNG images when the `output` path ends with `.png`:
371
374
 
372
- Output: `docs/er-diagram.png`
375
+ ```prisma
376
+ generator Hekireki-PNG {
377
+ provider = "hekireki-dbml"
378
+ output = "docs/er-diagram.png"
379
+ }
380
+ ```
381
+
382
+ ### Docs
383
+
384
+ The `hekireki-docs` generator creates an HTML documentation page from your Prisma schema. Serve it locally with `hekireki docs serve`:
385
+
386
+ ```prisma
387
+ generator Hekireki-Docs {
388
+ provider = "hekireki-docs"
389
+ output = "./docs"
390
+ }
391
+ ```
373
392
 
374
393
  ## Configuration
375
394
 
@@ -404,6 +423,7 @@ generator Hekireki-ArkType {
404
423
  file = "index.ts" // File name (default: index.ts)
405
424
  type = true // Generate TypeScript types (default: false)
406
425
  comment = true // Include schema documentation (default: false)
426
+ relation = true // Generate relation schemas (default: false)
407
427
  }
408
428
 
409
429
  // Effect Schema Generator
@@ -413,6 +433,7 @@ generator Hekireki-Effect {
413
433
  file = "index.ts" // File name (default: index.ts)
414
434
  type = true // Generate TypeScript types (default: false)
415
435
  comment = true // Include schema documentation (default: false)
436
+ relation = true // Generate relation schemas (default: false)
416
437
  }
417
438
 
418
439
  // Mermaid ER Generator
@@ -425,26 +446,45 @@ generator Hekireki-ER {
425
446
  // Ecto Generator
426
447
  generator Hekireki-Ecto {
427
448
  provider = "hekireki-ecto"
428
- output = "./ecto" // Output directory (default: ./ecto)
449
+ output = "./ecto" // Output directory (default: ./ecto/)
429
450
  app = "MyApp" // App name (default: MyApp)
430
451
  }
431
452
 
432
- // DBML Generator
453
+ // DBML Generator (output extension determines format: .dbml or .png)
433
454
  generator Hekireki-DBML {
434
455
  provider = "hekireki-dbml"
435
- output = "./dbml" // Output directory (default: ./dbml)
436
- file = "schema.dbml" // File name (default: schema.dbml)
456
+ output = "docs/schema.dbml" // File path ending in .dbml or .png
457
+ mapToDbSchema = true // Map to DB schema names (default: true)
458
+ }
459
+
460
+ // PNG output (same provider, different extension)
461
+ generator Hekireki-PNG {
462
+ provider = "hekireki-dbml"
463
+ output = "docs/er-diagram.png" // .png extension โ†’ PNG output
464
+ mapToDbSchema = true // Map to DB schema names (default: true)
437
465
  }
438
466
 
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)
467
+ // Docs Generator
468
+ generator Hekireki-Docs {
469
+ provider = "hekireki-docs"
470
+ output = "./docs" // Output directory (default: ./docs)
445
471
  }
446
472
  ```
447
473
 
474
+ ## Docs Server
475
+
476
+ Hekireki includes a built-in documentation server powered by [Hono](https://hono.dev/). After generating docs with `prisma generate`, you can preview them locally:
477
+
478
+ ```bash
479
+ # Start the docs server (default: http://localhost:5858)
480
+ hekireki docs serve
481
+
482
+ # Specify a custom port
483
+ hekireki docs serve -p 3000
484
+ ```
485
+
486
+ > **Note:** Run `prisma generate` first to generate the `docs/` directory with `index.html`.
487
+
448
488
  ## License
449
489
 
450
490
  Distributed under the MIT License. See [LICENSE](https://github.com/nakita628/hekireki?tab=MIT-1-ov-file) for more information.
package/dist/cli/index.js CHANGED
@@ -58,7 +58,7 @@ const parsePort = (args) => {
58
58
  error: "โŒ Error: --port requires a number"
59
59
  };
60
60
  const port = parseInt(portStr, 10);
61
- if (isNaN(port)) return {
61
+ if (Number.isNaN(port)) return {
62
62
  ok: false,
63
63
  error: `โŒ Error: Invalid port number: ${portStr}`
64
64
  };
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { A as writeFileBinary, D as stripAnnotations, O as mkdir, T as quote, a as generateEnum, c as generateRef, k as writeFile, o as generateIndex, r as escapeNote, s as generatePrismaColumn, t as combineKeys, u as getString } from "../../utils-0nIzqFtt.js";
3
+ import { dirname } from "node:path";
3
4
  import pkg from "@prisma/generator-helper";
4
5
  import { Resvg } from "@resvg/resvg-js";
5
6
  import { run } from "@softwaretechnik/dbml-renderer";
@@ -37,10 +38,10 @@ function generateTableIndexes(model) {
37
38
  isUnique: true
38
39
  }))];
39
40
  }
40
- function generateTables(models, mapToDbSchema = false, includeRelationFields = true) {
41
+ function generateTables(models, mapToDbSchema = false) {
41
42
  return models.map((model) => {
42
43
  const modelName = mapToDbSchema && model.dbName ? model.dbName : model.name;
43
- const columnLines = (includeRelationFields ? model.fields : model.fields.filter((field) => !field.relationName)).map((field) => toDBMLColumn(field, models, mapToDbSchema)).map(generatePrismaColumn).join("\n");
44
+ const columnLines = model.fields.map((field) => toDBMLColumn(field, models, mapToDbSchema)).map(generatePrismaColumn).join("\n");
44
45
  const indexes = generateTableIndexes(model);
45
46
  const indexBlock = indexes.length > 0 ? `\n\n indexes {\n${indexes.map(generateIndex).join("\n")}\n }` : "";
46
47
  const strippedNote = stripAnnotations(model.documentation);
@@ -79,8 +80,8 @@ function generateRelations(models, mapToDbSchema = false) {
79
80
  });
80
81
  }));
81
82
  }
82
- function dbmlContent(datamodel, mapToDbSchema = false, includeRelationFields = true) {
83
- const tables = generateTables(datamodel.models, mapToDbSchema, includeRelationFields);
83
+ function dbmlContent(datamodel, mapToDbSchema = false) {
84
+ const tables = generateTables(datamodel.models, mapToDbSchema);
84
85
  const enums = generateEnums(datamodel.enums);
85
86
  const refs = generateRelations(datamodel.models, mapToDbSchema);
86
87
  return [
@@ -98,8 +99,10 @@ const generateDbmlFile = async (outputDir, content, fileName) => {
98
99
  return { ok: true };
99
100
  };
100
101
  const generatePng = async (outputDir, dbml, fileName) => {
101
- const pngBuffer = new Resvg(run(dbml, "svg"), { font: { loadSystemFonts: true } }).render().asPng();
102
- const writeResult = await writeFileBinary(`${outputDir}/${fileName}`, pngBuffer);
102
+ return generatePngFile(`${outputDir}/${fileName}`, dbml);
103
+ };
104
+ const generatePngFile = async (outputPath, dbml) => {
105
+ const writeResult = await writeFileBinary(outputPath, new Resvg(run(dbml, "svg"), { font: { loadSystemFonts: true } }).render().asPng());
103
106
  if (!writeResult.ok) return {
104
107
  ok: false,
105
108
  error: `Failed to write PNG: ${writeResult.error}`
@@ -113,17 +116,30 @@ const { generatorHandler } = pkg;
113
116
  async function main(options) {
114
117
  const { config } = options.generator;
115
118
  const mapToDbSchema = getString(config?.mapToDbSchema) !== "false";
116
- const includeRelationFields = getString(config?.includeRelationFields) !== "false";
117
- const content = dbmlContent(options.dmmf.datamodel, mapToDbSchema, includeRelationFields);
119
+ const content = dbmlContent(options.dmmf.datamodel, mapToDbSchema);
118
120
  const output = options.generator.output?.value ?? "./dbml";
119
- const dbmlFile = getString(config?.file, "schema.dbml") ?? "schema.dbml";
120
- const pngFile = getString(config?.pngFile, "er-diagram.png") ?? "er-diagram.png";
121
- const mkdirResult = await mkdir(output);
122
- if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
123
- const dbmlResult = await generateDbmlFile(output, content, dbmlFile);
124
- if (!dbmlResult.ok) throw new Error(dbmlResult.error);
125
- const pngResult = await generatePng(output, content, pngFile);
126
- if (!pngResult.ok) throw new Error(pngResult.error);
121
+ if (output.endsWith(".png") || output.endsWith(".dbml")) {
122
+ const mkdirResult = await mkdir(dirname(output));
123
+ if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
124
+ if (output.endsWith(".png")) {
125
+ const pngResult = await generatePngFile(output, content);
126
+ if (!pngResult.ok) throw new Error(pngResult.error);
127
+ } else {
128
+ const dbmlResult = await writeFile(output, content);
129
+ if (!dbmlResult.ok) throw new Error(`Failed to write DBML: ${dbmlResult.error}`);
130
+ }
131
+ } else {
132
+ const file = getString(config?.file, "schema.dbml") ?? "schema.dbml";
133
+ const mkdirResult = await mkdir(output);
134
+ if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
135
+ if (file.endsWith(".png")) {
136
+ const pngResult = await generatePng(output, content, file);
137
+ if (!pngResult.ok) throw new Error(pngResult.error);
138
+ } else {
139
+ const dbmlResult = await generateDbmlFile(output, content, file);
140
+ if (!dbmlResult.ok) throw new Error(dbmlResult.error);
141
+ }
142
+ }
127
143
  }
128
144
  generatorHandler({
129
145
  onManifest() {
@@ -25,19 +25,11 @@ const getMappings = (mappings, datamodel) => {
25
25
  upsert: mapping.upsertOne || mapping.upsertSingle || mapping.upsert
26
26
  }));
27
27
  };
28
- const transformDMMF = (dmmf, { includeRelationFields }) => {
29
- let datamodel = dmmf.datamodel;
30
- if (!includeRelationFields) datamodel = {
31
- ...dmmf.datamodel,
32
- models: dmmf.datamodel.models.map((model) => ({
33
- ...model,
34
- fields: model.fields.filter((field) => !field.relationName)
35
- }))
36
- };
28
+ const transformDMMF = (dmmf) => {
37
29
  return {
38
30
  ...dmmf,
39
- datamodel,
40
- mappings: getMappings(dmmf.mappings, datamodel)
31
+ datamodel: dmmf.datamodel,
32
+ mappings: getMappings(dmmf.mappings, dmmf.datamodel)
41
33
  };
42
34
  };
43
35
 
@@ -138,7 +130,6 @@ const darkModeToggleLabelClass = css`
138
130
  `;
139
131
  const iconClass = css`
140
132
  color: var(--icon-color);
141
- cursor: pointer;
142
133
  `;
143
134
  const h1Class = css`
144
135
  font-size: 1.875rem;
@@ -167,9 +158,6 @@ const h4Class = css`
167
158
  const textClass = css`
168
159
  color: var(--text-primary);
169
160
  `;
170
- const textMutedClass = css`
171
- color: var(--text-muted);
172
- `;
173
161
  const tableClass = css`
174
162
  border-collapse: collapse;
175
163
  table-layout: auto;
@@ -263,26 +251,12 @@ const hrSmallClass = css`
263
251
  border: none;
264
252
  border-top: 1px solid var(--border-color);
265
253
  `;
266
- const listClass = css`
267
- margin: 0;
268
- padding: 0;
269
- list-style: none;
270
- `;
271
254
  const listItemClass = css`
272
255
  margin-bottom: 1rem;
273
256
  `;
274
257
  const mb2Class = css`
275
258
  margin-bottom: 0.5rem;
276
259
  `;
277
- const mb4Class = css`
278
- margin-bottom: 1rem;
279
- `;
280
- const mt2Class = css`
281
- margin-top: 0.5rem;
282
- `;
283
- const mt4Class = css`
284
- margin-top: 1rem;
285
- `;
286
260
  const ml4Class = css`
287
261
  margin-left: 1rem;
288
262
  `;
@@ -373,8 +347,7 @@ const getOutputTypes = (dmmfOutputTypes) => dmmfOutputTypes.map((outputType) =>
373
347
  nullable: !op.isNullable,
374
348
  type: [{
375
349
  isList: op.outputType.isList,
376
- type: op.outputType.type,
377
- location: op.outputType.location
350
+ type: op.outputType.type
378
351
  }]
379
352
  }))
380
353
  }));
@@ -401,6 +374,9 @@ const ModelAction = {
401
374
  updateMany: "updateMany",
402
375
  upsert: "upsert"
403
376
  };
377
+ function isFieldDefault(v) {
378
+ return typeof v === "object" && v !== null && "name" in v && "args" in v;
379
+ }
404
380
  const fieldDirectiveMap = new Map([
405
381
  ["isUnique", "@unique"],
406
382
  ["isId", "@id"],
@@ -593,7 +569,7 @@ const getFieldDirectives = (field) => {
593
569
  if (mappedDirectiveValue) if (k === "hasDefaultValue" && field.default !== void 0) {
594
570
  if (typeof field.default === "string" || typeof field.default === "number" || typeof field.default === "boolean") directives.push(`${mappedDirectiveValue}(${field.default})`);
595
571
  else if (Array.isArray(field.default)) directives.push(`${mappedDirectiveValue}([${field.default.toString()}])`);
596
- else if (typeof field.default === "object") directives.push(`${mappedDirectiveValue}(${field.default.name}(${field.default.args.join(",")}))`);
572
+ else if (isFieldDefault(field.default)) directives.push(`${mappedDirectiveValue}(${field.default.name}(${field.default.args.join(",")}))`);
597
573
  } else directives.push(mappedDirectiveValue);
598
574
  });
599
575
  return directives;
@@ -883,7 +859,7 @@ const createTOC = (d) => {
883
859
 
884
860
  //#endregion
885
861
  //#region src/generator/docs/printer/index.tsx
886
- const HekirekiSvg = () => /* @__PURE__ */ jsxs("svg", {
862
+ const HekirekiLogo = () => /* @__PURE__ */ jsxs("svg", {
887
863
  width: "40",
888
864
  height: "40",
889
865
  viewBox: "0 0 40 40",
@@ -953,7 +929,7 @@ const Sidebar = ({ toc }) => /* @__PURE__ */ jsxs("div", {
953
929
  class: headerClass,
954
930
  children: [/* @__PURE__ */ jsxs("div", {
955
931
  class: logoContainerClass,
956
- children: [/* @__PURE__ */ jsx(HekirekiSvg, {}), /* @__PURE__ */ jsx("span", {
932
+ children: [/* @__PURE__ */ jsx(HekirekiLogo, {}), /* @__PURE__ */ jsx("span", {
957
933
  class: logoTextClass,
958
934
  children: "Hekireki Docs"
959
935
  })]
@@ -985,9 +961,7 @@ generatorHandler({
985
961
  };
986
962
  },
987
963
  async onGenerate(options) {
988
- const { config } = options.generator;
989
- const includeRelationFields = config.includeRelationFields !== "false";
990
- const html = await generateHTML(transformDMMF(options.dmmf, { includeRelationFields }));
964
+ const html = await generateHTML(transformDMMF(options.dmmf));
991
965
  const output = options.generator.output?.value;
992
966
  if (!output) throw new Error("No output was specified for Hekireki Docs Generator");
993
967
  await fs.mkdir(output, { recursive: true });
@@ -36,7 +36,7 @@ function extractRelationsFromDmmf(models) {
36
36
  return models.flatMap((model) => model.fields.filter((field) => field.kind === "object" && field.relationFromFields && field.relationFromFields.length > 0).map((field) => {
37
37
  const toModel = model.name;
38
38
  const fromModel = field.type;
39
- const toField = field.relationFromFields[0];
39
+ const toField = field.relationFromFields?.[0];
40
40
  const fromField = field.relationToFields?.[0] ?? "id";
41
41
  const fromCardinality = "one";
42
42
  const toCardinality = (models.find((m) => m.name === fromModel)?.fields.find((f) => f.relationName === field.relationName && f.name !== field.name))?.isList ? field.isRequired ? "many" : "zero-many" : field.isRequired ? "one" : "zero-one";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hekireki",
3
3
  "type": "module",
4
- "version": "0.6.0",
4
+ "version": "0.6.1",
5
5
  "license": "MIT",
6
6
  "description": "Hekireki is a tool that generates validation schemas for Zod, Valibot, ArkType, and Effect Schema, as well as ER diagrams and DBML, from Prisma schemas annotated with comments.",
7
7
  "keywords": [
@@ -51,25 +51,26 @@
51
51
  "release": "npm pkg fix && pnpm build && npm publish"
52
52
  },
53
53
  "dependencies": {
54
- "@hono/node-server": "^1.14.1",
55
- "@prisma/generator-helper": "^7.0.1",
54
+ "@hono/node-server": "^1.19.9",
55
+ "@prisma/generator-helper": "^7.4.0",
56
56
  "@resvg/resvg-js": "^2.6.2",
57
57
  "@softwaretechnik/dbml-renderer": "^1.0.31",
58
- "hono": "^4.7.4",
59
- "oxfmt": "^0.27.0"
58
+ "hono": "^4.11.9",
59
+ "oxfmt": "^0.32.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@prisma/client": "^7.0.1",
63
- "@types/node": "^24.10.1",
64
- "@typescript/native-preview": "7.0.0-dev.20260130.1",
65
- "@vitest/coverage-v8": "^4.0.14",
62
+ "@prisma/client": "^7.4.0",
63
+ "@types/node": "^25.2.3",
64
+ "@typescript/native-preview": "7.0.0-dev.20260214.1",
65
+ "@vitest/coverage-v8": "^4.0.18",
66
66
  "arktype": "^2.1.29",
67
- "effect": "^3.19.15",
68
- "prisma": "^7.0.1",
69
- "tsdown": "^0.20.1",
70
- "tsx": "^4.20.6",
67
+ "effect": "^3.19.17",
68
+ "prisma": "^7.4.0",
69
+ "tsdown": "^0.20.3",
70
+ "tsx": "^4.21.0",
71
+ "typescript": "^5.9.3",
71
72
  "valibot": "1.2.0",
72
- "vitest": "^4.0.14",
73
- "zod": "^4.1.13"
73
+ "vitest": "^4.0.18",
74
+ "zod": "^4.3.6"
74
75
  }
75
76
  }