hekireki 0.5.1 โ†’ 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,6 +1,6 @@
1
1
  import { GeneratorOptions } from "@prisma/generator-helper";
2
2
 
3
3
  //#region src/generator/arktype/index.d.ts
4
- declare const onGenerate: (options: GeneratorOptions) => Promise<void>;
4
+ declare function main(options: GeneratorOptions): Promise<void>;
5
5
  //#endregion
6
- export { onGenerate };
6
+ export { main };
@@ -1,73 +1,16 @@
1
1
  #!/usr/bin/env node
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";
2
+ import { n as validationSchemas, r as fmt, t as makeRelationsOnly } from "../../prisma-vPHUnYiY.js";
3
+ import { C as parseDocumentWithoutAnnotations, O as mkdir, b as makeValidationExtractor, k as writeFile, l as getBool, m as makeArktypeSchemas, p as makeArktypeInfer, u as getString } from "../../utils-0nIzqFtt.js";
4
4
  import path from "node:path";
5
5
  import pkg from "@prisma/generator-helper";
6
- import { makeValidationExtractor } from "utils-lab";
7
6
 
8
- //#region src/generator/arktype/generator/schema.ts
9
- /**
10
- * Generate ArkType schema
11
- * @param modelName - The name of the model
12
- * @param fields - The fields of the model
13
- * @returns The generated ArkType schema
14
- */
15
- function schema(modelName, fields) {
16
- return `export const ${modelName}Schema = type({\n${fields}\n})`;
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
- */
7
+ //#region src/helper/arktype.ts
25
8
  function makeArktypeRelations(model, relProps, options) {
26
9
  if (relProps.length === 0) return null;
27
10
  const fields = `${`...${model.name}Schema.t,`}${relProps.map((r) => `${r.key}:${r.isMany ? `${r.targetModel}Schema.array()` : `${r.targetModel}Schema`},`).join("")}`;
28
11
  const typeLine = options?.includeType ? `\n\nexport type ${model.name}Relations = typeof ${model.name}RelationsSchema.infer` : "";
29
12
  return `export const ${model.name}RelationsSchema = type({${fields}})${typeLine}`;
30
13
  }
31
-
32
- //#endregion
33
- //#region src/generator/arktype/generator/schemas.ts
34
- /**
35
- * Generate properties for ArkType schema.
36
- */
37
- function arktypePropertiesGenerator(fields, comment) {
38
- return fields.map((field) => {
39
- return `${comment && field.comment.length > 0 ? `${field.comment.map((c) => ` /** ${c} */`).join("\n")}\n` : ""} ${field.fieldName}: ${field.validation ?? "\"unknown\""},`;
40
- }).join("\n");
41
- }
42
- /**
43
- * Creates ArkType schemas from model fields.
44
- *
45
- * @param modelFields - The fields of the model
46
- * @param comment - Whether to include comments in the generated code
47
- * @returns The generated ArkType schemas
48
- */
49
- function schemas(modelFields, comment) {
50
- return schemaFromFields(modelFields, comment, schema, arktypePropertiesGenerator);
51
- }
52
-
53
- //#endregion
54
- //#region src/generator/arktype/generator/arktype.ts
55
- /**
56
- * Generate ArkType infer type statement.
57
- * @param modelName - The name of the model
58
- * @returns The generated type inference statement
59
- */
60
- function makeArktypeInfer(modelName) {
61
- return `export type ${modelName} = typeof ${modelName}Schema.infer`;
62
- }
63
- /**
64
- * Creates ArkType schemas and types from models.
65
- *
66
- * @param models - The models to generate the ArkType schemas and types for
67
- * @param type - Whether to generate types
68
- * @param comment - Whether to include comments in the generated code
69
- * @returns The generated ArkType schemas and types
70
- */
71
14
  function arktype(models, type, comment) {
72
15
  return validationSchemas(models, type, comment, {
73
16
  importStatement: `import { type } from 'arktype'`,
@@ -75,38 +18,24 @@ function arktype(models, type, comment) {
75
18
  parseDocument: parseDocumentWithoutAnnotations,
76
19
  extractValidation: makeValidationExtractor("@a."),
77
20
  inferType: makeArktypeInfer,
78
- schemas
21
+ schemas: makeArktypeSchemas
79
22
  });
80
23
  }
81
24
 
82
25
  //#endregion
83
26
  //#region src/generator/arktype/index.ts
84
27
  const { generatorHandler } = pkg;
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) => {
28
+ async function main(options) {
100
29
  const outDir = options.generator.output?.value ?? "./arktype";
101
30
  const file = getString(options.generator.config?.file, "index.ts") ?? "index.ts";
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"));
31
+ const enableRelation = options.generator.config?.relation === "true" || Array.isArray(options.generator.config?.relation) && options.generator.config?.relation[0] === "true";
32
+ const fmtResult = await fmt([arktype(options.dmmf.datamodel.models, getBool(options.generator.config?.type), getBool(options.generator.config?.comment)), enableRelation ? makeRelationsOnly(options.dmmf, getBool(options.generator.config?.type), makeArktypeRelations) : ""].filter(Boolean).join("\n\n"));
103
33
  if (!fmtResult.ok) throw new Error(`Format error: ${fmtResult.error}`);
104
34
  const mkdirResult = await mkdir(outDir);
105
35
  if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
106
36
  const writeResult = await writeFile(path.join(outDir, file), fmtResult.value);
107
37
  if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
108
- };
109
- const onGenerate = (options) => emit(options, options.generator.config?.relation === "true" || Array.isArray(options.generator.config?.relation) && options.generator.config?.relation[0] === "true");
38
+ }
110
39
  generatorHandler({
111
40
  onManifest() {
112
41
  return {
@@ -114,8 +43,8 @@ generatorHandler({
114
43
  prettyName: "Hekireki-ArkType"
115
44
  };
116
45
  },
117
- onGenerate
46
+ onGenerate: main
118
47
  });
119
48
 
120
49
  //#endregion
121
- export { onGenerate };
50
+ export { main };
@@ -1,9 +1,6 @@
1
1
  import { GeneratorOptions } from "@prisma/generator-helper";
2
2
 
3
3
  //#region src/generator/dbml/index.d.ts
4
- /**
5
- * Main generator function
6
- */
7
- declare const main: (options: GeneratorOptions) => Promise<void>;
4
+ declare function main(options: GeneratorOptions): Promise<void>;
8
5
  //#endregion
9
6
  export { main };
@@ -1,93 +1,53 @@
1
1
  #!/usr/bin/env node
2
- import { n as writeFile, r as writeFileBinary, t as mkdir } from "../../fsp-FOfmtPYd.js";
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
- import { escapeNote, formatConstraints, generateEnum, generateIndex, generateRef, quote } from "utils-lab";
5
5
  import { Resvg } from "@resvg/resvg-js";
6
6
  import { run } from "@softwaretechnik/dbml-renderer";
7
7
 
8
- //#region src/generator/dbml/generator/dbml-content.ts
9
- /**
10
- * Strip validation annotations (@z.*, @v.*, @a.*, @e.*) and relation annotations (@relation) from documentation
11
- */
12
- function stripAnnotations(doc) {
13
- if (!doc) return void 0;
14
- const result = doc.split("\n").filter((line) => {
15
- const trimmed = line.trim();
16
- return !(trimmed.startsWith("@z.") || trimmed.startsWith("@v.") || trimmed.startsWith("@a.") || trimmed.startsWith("@e.") || trimmed.startsWith("@relation"));
17
- }).join("\n").trim();
18
- return result.length > 0 ? result : void 0;
8
+ //#region src/helper/dbml.ts
9
+ function resolveFieldType(field, models, mapToDbSchema) {
10
+ const baseType = mapToDbSchema ? models.find((m) => m.name === field.type)?.dbName ?? field.type : field.type;
11
+ return field.isList && !field.relationName ? `${baseType}[]` : baseType;
12
+ }
13
+ function resolveDefaultValue(field) {
14
+ const defaultDef = field.default;
15
+ if (defaultDef?.name === "autoincrement") return void 0;
16
+ if (defaultDef?.name === "now") return "`now()`";
17
+ if (field.hasDefaultValue && typeof field.default !== "object") return field.type === "String" || field.type === "Json" || field.kind === "enum" ? `'${field.default}'` : String(field.default);
19
18
  }
20
- /**
21
- * Convert Prisma field to DBMLColumn
22
- */
23
19
  function toDBMLColumn(field, models, mapToDbSchema) {
24
- let fieldType = field.type;
25
- if (mapToDbSchema) {
26
- const relatedModel = models.find((m) => m.name === field.type);
27
- if (relatedModel?.dbName) fieldType = relatedModel.dbName;
28
- }
29
- if (field.isList && !field.relationName) fieldType = `${fieldType}[]`;
30
- let defaultValue;
31
20
  const defaultDef = field.default;
32
- if (defaultDef?.name === "autoincrement") {} else if (defaultDef?.name === "now") defaultValue = "`now()`";
33
- else if (field.hasDefaultValue && typeof field.default !== "object") if (field.type === "String" || field.type === "Json" || field.kind === "enum") defaultValue = `'${field.default}'`;
34
- else defaultValue = String(field.default);
35
21
  return {
36
22
  name: field.name,
37
- type: fieldType,
23
+ type: resolveFieldType(field, models, mapToDbSchema),
38
24
  isPrimaryKey: field.isId,
39
25
  isIncrement: defaultDef?.name === "autoincrement",
40
26
  isUnique: field.isUnique,
41
27
  isNotNull: field.isRequired && !field.isId,
42
- defaultValue,
28
+ defaultValue: resolveDefaultValue(field),
43
29
  note: stripAnnotations(field.documentation)
44
30
  };
45
31
  }
46
- /**
47
- * Generate custom column line with Prisma-specific formatting
48
- */
49
- function generatePrismaColumn(column) {
50
- const constraints = [];
51
- if (column.isPrimaryKey) constraints.push("pk");
52
- if (column.isIncrement) constraints.push("increment");
53
- if (column.defaultValue !== void 0) constraints.push(`default: ${column.defaultValue}`);
54
- if (column.isUnique) constraints.push("unique");
55
- if (column.isNotNull) constraints.push("not null");
56
- if (column.note) constraints.push(`note: ${quote(column.note)}`);
57
- return ` ${column.name} ${column.type}${formatConstraints(constraints)}`;
58
- }
59
- /**
60
- * Generate table indexes block
61
- */
62
32
  function generateTableIndexes(model) {
63
- const indexes = [];
64
- const primaryFields = model.primaryKey?.fields;
65
- if (primaryFields && primaryFields.length > 0) indexes.push({
66
- columns: primaryFields,
33
+ return [...model.primaryKey?.fields && model.primaryKey.fields.length > 0 ? [{
34
+ columns: model.primaryKey.fields,
67
35
  isPrimaryKey: true
68
- });
69
- for (const composite of model.uniqueFields) if (composite.length > 1) indexes.push({
70
- columns: composite,
36
+ }] : [], ...model.uniqueFields.filter((c) => c.length > 1).map((c) => ({
37
+ columns: c,
71
38
  isUnique: true
72
- });
73
- return indexes;
39
+ }))];
74
40
  }
75
- /**
76
- * Generate table definitions
77
- */
78
- function generateTables(models, mapToDbSchema = false, includeRelationFields = true) {
41
+ function generateTables(models, mapToDbSchema = false) {
79
42
  return models.map((model) => {
80
43
  const modelName = mapToDbSchema && model.dbName ? model.dbName : model.name;
81
- 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");
82
45
  const indexes = generateTableIndexes(model);
83
46
  const indexBlock = indexes.length > 0 ? `\n\n indexes {\n${indexes.map(generateIndex).join("\n")}\n }` : "";
84
47
  const strippedNote = stripAnnotations(model.documentation);
85
48
  return `Table ${modelName} {\n${columnLines}${indexBlock}${strippedNote ? `\n\n Note: ${quote(escapeNote(strippedNote))}` : ""}\n}`;
86
49
  });
87
50
  }
88
- /**
89
- * Generate enum definitions
90
- */
91
51
  function generateEnums(enums) {
92
52
  return enums.map((e) => {
93
53
  return generateEnum({
@@ -96,53 +56,32 @@ function generateEnums(enums) {
96
56
  });
97
57
  });
98
58
  }
99
- /**
100
- * Get relation operator based on cardinality
101
- */
102
59
  function getRelationOperator(models, from, to) {
103
60
  return (models.find((m) => m.name === to)?.fields.find((f) => f.type === from))?.isList ? ">" : "-";
104
61
  }
105
- /**
106
- * Combine keys for composite foreign keys
107
- */
108
- function combineKeys(keys) {
109
- return keys.length > 1 ? `(${keys.join(", ")})` : keys[0];
110
- }
111
- /**
112
- * Generate foreign key references
113
- */
114
62
  function generateRelations(models, mapToDbSchema = false) {
115
- const refs = [];
116
- for (const model of models) {
117
- const relFields = model.fields.filter((field) => field.relationName && field.relationToFields?.length && field.relationFromFields?.length);
118
- for (const field of relFields) {
119
- const relationFrom = model.name;
120
- const relationTo = field.type;
121
- const operator = getRelationOperator(models, relationFrom, relationTo);
122
- const relationFromName = mapToDbSchema && model.dbName ? model.dbName : model.name;
123
- const relatedModel = models.find((m) => m.name === relationTo);
124
- const relationToName = mapToDbSchema && relatedModel?.dbName ? relatedModel.dbName : relationTo;
125
- const fromColumn = combineKeys(field.relationFromFields ?? []);
126
- const toColumn = combineKeys(field.relationToFields ?? []);
127
- const ref = {
128
- name: `${relationFromName}_${fromColumn}_fk`,
129
- fromTable: relationFromName,
130
- fromColumn,
131
- toTable: relationToName,
132
- toColumn,
133
- type: operator,
134
- onDelete: field.relationOnDelete
135
- };
136
- refs.push(generateRef(ref));
137
- }
138
- }
139
- return refs;
63
+ return models.flatMap((model) => model.fields.filter((field) => field.relationName && field.relationToFields?.length && field.relationFromFields?.length).map((field) => {
64
+ const relationFrom = model.name;
65
+ const relationTo = field.type;
66
+ const operator = getRelationOperator(models, relationFrom, relationTo);
67
+ const relationFromName = mapToDbSchema && model.dbName ? model.dbName : model.name;
68
+ const relatedModel = models.find((m) => m.name === relationTo);
69
+ const relationToName = mapToDbSchema && relatedModel?.dbName ? relatedModel.dbName : relationTo;
70
+ const fromColumn = combineKeys(field.relationFromFields ?? []);
71
+ const toColumn = combineKeys(field.relationToFields ?? []);
72
+ return generateRef({
73
+ name: `${relationFromName}_${fromColumn}_fk`,
74
+ fromTable: relationFromName,
75
+ fromColumn,
76
+ toTable: relationToName,
77
+ toColumn,
78
+ type: operator,
79
+ onDelete: field.relationOnDelete
80
+ });
81
+ }));
140
82
  }
141
- /**
142
- * Generate complete DBML content from Prisma DMMF
143
- */
144
- function dbmlContent(datamodel, mapToDbSchema = false, includeRelationFields = true) {
145
- const tables = generateTables(datamodel.models, mapToDbSchema, includeRelationFields);
83
+ function dbmlContent(datamodel, mapToDbSchema = false) {
84
+ const tables = generateTables(datamodel.models, mapToDbSchema);
146
85
  const enums = generateEnums(datamodel.enums);
147
86
  const refs = generateRelations(datamodel.models, mapToDbSchema);
148
87
  return [
@@ -151,25 +90,7 @@ function dbmlContent(datamodel, mapToDbSchema = false, includeRelationFields = t
151
90
  ...refs
152
91
  ].join("\n\n");
153
92
  }
154
-
155
- //#endregion
156
- //#region src/generator/dbml/index.ts
157
- const { generatorHandler } = pkg;
158
- /**
159
- * Get string value from config
160
- */
161
- const getStringValue = (value) => value === void 0 ? void 0 : Array.isArray(value) ? value[0] : value;
162
- /**
163
- * Get boolean option from config
164
- */
165
- const getBoolOption = (config, key, defaultValue) => {
166
- const value = getStringValue(config[key]);
167
- return value === void 0 ? defaultValue : value.toLowerCase() !== "false";
168
- };
169
- /**
170
- * Generate DBML file
171
- */
172
- const generateDbml = async (outputDir, content, fileName) => {
93
+ const generateDbmlFile = async (outputDir, content, fileName) => {
173
94
  const writeResult = await writeFile(`${outputDir}/${fileName}`, content);
174
95
  if (!writeResult.ok) return {
175
96
  ok: false,
@@ -177,36 +98,49 @@ const generateDbml = async (outputDir, content, fileName) => {
177
98
  };
178
99
  return { ok: true };
179
100
  };
180
- /**
181
- * Generate PNG from DBML
182
- */
183
101
  const generatePng = async (outputDir, dbml, fileName) => {
184
- const pngBuffer = new Resvg(run(dbml, "svg"), { font: { loadSystemFonts: true } }).render().asPng();
185
- 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());
186
106
  if (!writeResult.ok) return {
187
107
  ok: false,
188
108
  error: `Failed to write PNG: ${writeResult.error}`
189
109
  };
190
110
  return { ok: true };
191
111
  };
192
- /**
193
- * Main generator function
194
- */
195
- const main = async (options) => {
112
+
113
+ //#endregion
114
+ //#region src/generator/dbml/index.ts
115
+ const { generatorHandler } = pkg;
116
+ async function main(options) {
196
117
  const { config } = options.generator;
197
- const mapToDbSchema = getBoolOption(config, "mapToDbSchema", true);
198
- const includeRelationFields = getBoolOption(config, "includeRelationFields", true);
199
- const content = dbmlContent(options.dmmf.datamodel, mapToDbSchema, includeRelationFields);
118
+ const mapToDbSchema = getString(config?.mapToDbSchema) !== "false";
119
+ const content = dbmlContent(options.dmmf.datamodel, mapToDbSchema);
200
120
  const output = options.generator.output?.value ?? "./dbml";
201
- const dbmlFile = getStringValue(config.file) ?? "schema.dbml";
202
- const pngFile = getStringValue(config.pngFile) ?? "er-diagram.png";
203
- const mkdirResult = await mkdir(output);
204
- if (!mkdirResult.ok) throw new Error(`โŒ Failed to create directory: ${mkdirResult.error}`);
205
- const dbmlResult = await generateDbml(output, content, dbmlFile);
206
- if (!dbmlResult.ok) throw new Error(`โŒ ${dbmlResult.error}`);
207
- const pngResult = await generatePng(output, content, pngFile);
208
- if (!pngResult.ok) throw new Error(`โŒ ${pngResult.error}`);
209
- };
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
+ }
143
+ }
210
144
  generatorHandler({
211
145
  onManifest() {
212
146
  return {