hekireki 0.8.0 → 0.8.2

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
@@ -712,6 +712,25 @@ generator Hekireki-PNG {
712
712
  }
713
713
  ```
714
714
 
715
+ ### Logical Relations (without a Foreign Key)
716
+
717
+ To draw a relation that has **no physical foreign key**, add a `/// @relation <Parent>.<field> <Child>.<field> <cardinality>` doc-comment on the model:
718
+
719
+ ```prisma
720
+ model User {
721
+ id String @id @default(uuid())
722
+ name String
723
+ }
724
+
725
+ /// @relation User.id Post.userId one-to-many
726
+ model Post {
727
+ id String @id @default(uuid())
728
+ userId String
729
+ }
730
+ ```
731
+
732
+ The relation is drawn in both the Mermaid and DBML output even though `Post.userId` has no `@relation(...)` foreign key. When a physical FK and an annotation describe the same pair, the annotation's cardinality wins in the Mermaid diagram.
733
+
715
734
  ### Docs
716
735
 
717
736
  The `hekireki-docs` generator creates an HTML documentation page from your Prisma schema. Serve it locally with `hekireki docs serve`:
package/dist/bin/ajv.js CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/ajv.ts
5
4
  registerGenerator("ajv");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/arktype.ts
5
4
  registerGenerator("arktype");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
package/dist/bin/dbml.js CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/dbml.ts
5
4
  registerGenerator("dbml");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
package/dist/bin/docs.js CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/docs.ts
5
4
  registerGenerator("docs");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/drizzle.ts
5
4
  registerGenerator("drizzle");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
package/dist/bin/ecto.js CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/ecto.ts
5
4
  registerGenerator("ecto");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/effect.ts
5
4
  registerGenerator("effect");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
package/dist/bin/gorm.js CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/gorm.ts
5
4
  registerGenerator("gorm");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/mermaid-er.ts
5
4
  registerGenerator("mermaid-er");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/sea-orm.ts
5
4
  registerGenerator("sea-orm");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/sqlalchemy.ts
5
4
  registerGenerator("sqlalchemy");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/typebox.ts
5
4
  registerGenerator("typebox");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/valibot.ts
5
4
  registerGenerator("valibot");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
package/dist/bin/zod.js CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-BDZ9aq7E.js";
3
-
2
+ import { t as registerGenerator } from "../bin-B_9WDdSx.js";
4
3
  //#region src/bin/zod.ts
5
4
  registerGenerator("zod");
6
-
7
5
  //#endregion
8
- export { };
6
+ export {};
@@ -1,4 +1,4 @@
1
- import fs from "node:fs";
1
+ import "node:fs";
2
2
  import path from "node:path";
3
3
  import pkg from "@prisma/generator-helper";
4
4
  import { format } from "oxfmt";
@@ -8,7 +8,6 @@ import { run } from "@softwaretechnik/dbml-renderer";
8
8
  import { Style, css } from "hono/css";
9
9
  import { raw } from "hono/html";
10
10
  import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
11
-
12
11
  //#region src/format/index.ts
13
12
  async function fmt(input) {
14
13
  const { code, errors } = await format("<stdin>.ts", input, {
@@ -25,7 +24,6 @@ async function fmt(input) {
25
24
  value: code
26
25
  };
27
26
  }
28
-
29
27
  //#endregion
30
28
  //#region src/fsp/index.ts
31
29
  async function mkdir(dir) {
@@ -70,7 +68,6 @@ async function writeFileBinary(path, data) {
70
68
  };
71
69
  }
72
70
  }
73
-
74
71
  //#endregion
75
72
  //#region src/emit/index.ts
76
73
  async function emit(code, dir, output) {
@@ -125,30 +122,20 @@ async function emitMany(files, dir) {
125
122
  value: void 0
126
123
  };
127
124
  }
128
-
129
- //#endregion
130
- //#region src/helper/extract-relations.ts
131
- function collectRelationProps(models) {
132
- return models.flatMap((m) => m.fields.filter((f) => f.kind === "object").map((f) => ({
133
- model: m.name,
134
- key: f.name,
135
- targetModel: f.type,
136
- isMany: f.isList
137
- })));
138
- }
139
- function makeRelationsOnly(dmmf, includeType, makeRelations) {
140
- const models = dmmf.datamodel.models;
141
- const relIndex = collectRelationProps(models);
142
- const relByModel = Object.groupBy(relIndex, (r) => r.model);
143
- return models.map((model) => makeRelations(model, (relByModel[model.name] ?? []).map(({ key, targetModel, isMany }) => ({
144
- key,
145
- targetModel,
146
- isMany
147
- })), { includeType })).filter((code) => Boolean(code)).join("\n\n");
148
- }
149
-
150
125
  //#endregion
151
126
  //#region src/utils/index.ts
127
+ function parseRelation(line) {
128
+ const match = line.trim().match(/^@relation\s+(\w+)\.(\w+)\s+(\w+)\.(\w+)\s+(\w+-to-\w+)$/);
129
+ if (!match) return null;
130
+ const [, fromModel, fromField, toModel, toField, type] = match;
131
+ return {
132
+ fromModel,
133
+ fromField,
134
+ toModel,
135
+ toField,
136
+ type
137
+ };
138
+ }
152
139
  function getString(v, fallback) {
153
140
  return typeof v === "string" ? v : Array.isArray(v) ? v[0] ?? fallback : fallback;
154
141
  }
@@ -245,7 +232,6 @@ function schemaFromFields(modelFields, comment, schemaBuilder, propertiesGenerat
245
232
  const modelName = modelFields[0].modelName;
246
233
  return schemaBuilder(modelName, propertiesGenerator(modelFields, comment), objectType);
247
234
  }
248
-
249
235
  //#endregion
250
236
  //#region src/helper/validation-schema.ts
251
237
  function validationSchemas(models, type, comment, config) {
@@ -300,9 +286,8 @@ function validationSchemas(models, type, comment, config) {
300
286
  schemas
301
287
  ].join("\n") : schemas;
302
288
  }
303
-
304
289
  //#endregion
305
- //#region src/core/ajv.ts
290
+ //#region src/helper/ajv.ts
306
291
  function makeAjvInfer(modelName) {
307
292
  return `export type ${modelName} = FromSchema<typeof ${modelName}Schema>`;
308
293
  }
@@ -348,9 +333,33 @@ function ajvSchemaCode(models, type, comment, enums) {
348
333
  formatEnum: makeAjvEnumExpression
349
334
  });
350
335
  }
336
+ //#endregion
337
+ //#region src/helper/extract-relations.ts
338
+ function collectRelationProps(models) {
339
+ return models.flatMap((m) => m.fields.filter((f) => f.kind === "object").map((f) => ({
340
+ model: m.name,
341
+ key: f.name,
342
+ targetModel: f.type,
343
+ isMany: f.isList
344
+ })));
345
+ }
346
+ function makeRelationsOnly(dmmf, includeType, makeRelations) {
347
+ const models = dmmf.datamodel.models;
348
+ const relIndex = collectRelationProps(models);
349
+ const relByModel = Object.groupBy(relIndex, (r) => r.model);
350
+ return models.map((model) => makeRelations(model, (relByModel[model.name] ?? []).map(({ key, targetModel, isMany }) => ({
351
+ key,
352
+ targetModel,
353
+ isMany
354
+ })), { includeType })).filter((code) => Boolean(code)).join("\n\n");
355
+ }
356
+ //#endregion
357
+ //#region src/generator/ajv.ts
351
358
  function ajvCode(dmmf, type, comment, relation) {
352
359
  return [ajvSchemaCode(dmmf.datamodel.models, type, comment, dmmf.datamodel.enums), relation ? makeRelationsOnly(dmmf, type, makeAjvRelations) : ""].filter(Boolean).join("\n\n");
353
360
  }
361
+ //#endregion
362
+ //#region src/core/ajv.ts
354
363
  async function ajv(options) {
355
364
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
356
365
  ok: false,
@@ -366,9 +375,8 @@ async function ajv(options) {
366
375
  };
367
376
  return emit(ajvCode(options.dmmf, getBool(options.generator.config?.type), getBool(options.generator.config?.comment), getBool(options.generator.config?.relation)), resolved.dir, resolved.file);
368
377
  }
369
-
370
378
  //#endregion
371
- //#region src/core/arktype.ts
379
+ //#region src/helper/arktype.ts
372
380
  function makeArktypeInfer(modelName) {
373
381
  return `export type ${modelName} = typeof ${modelName}Schema.infer`;
374
382
  }
@@ -417,9 +425,13 @@ function arktypeSchemaCode(models, type, comment, enums) {
417
425
  formatEnum: makeArktypeEnumExpression
418
426
  });
419
427
  }
428
+ //#endregion
429
+ //#region src/generator/arktype.ts
420
430
  function arktypeCode(dmmf, type, comment, relation) {
421
431
  return [arktypeSchemaCode(dmmf.datamodel.models, type, comment, dmmf.datamodel.enums), relation ? makeRelationsOnly(dmmf, type, makeArktypeRelations) : ""].filter(Boolean).join("\n\n");
422
432
  }
433
+ //#endregion
434
+ //#region src/core/arktype.ts
423
435
  async function arktype(options) {
424
436
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
425
437
  ok: false,
@@ -435,9 +447,70 @@ async function arktype(options) {
435
447
  };
436
448
  return emit(arktypeCode(options.dmmf, getBool(options.generator.config?.type), getBool(options.generator.config?.comment), getBool(options.generator.config?.relation)), resolved.dir, resolved.file);
437
449
  }
438
-
439
450
  //#endregion
440
- //#region src/core/dbml.ts
451
+ //#region src/helper/relation.ts
452
+ function isCardinality(value) {
453
+ return value === "zero-one" || value === "one" || value === "zero-many" || value === "many";
454
+ }
455
+ function erKey(relation) {
456
+ return `${relation.from.model}.${relation.from.field}->${relation.to.model}.${relation.to.field}`;
457
+ }
458
+ function inferredERRelations(models) {
459
+ return models.flatMap((model) => model.fields.filter((field) => field.kind === "object" && field.relationFromFields && field.relationFromFields.length > 0).map((field) => {
460
+ const toModel = model.name;
461
+ const fromModel = field.type;
462
+ const toField = field.relationFromFields?.[0] ?? "";
463
+ const fromField = field.relationToFields?.[0] ?? "id";
464
+ 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";
465
+ return {
466
+ from: {
467
+ model: fromModel,
468
+ field: fromField,
469
+ cardinality: "one"
470
+ },
471
+ to: {
472
+ model: toModel,
473
+ field: toField,
474
+ cardinality: toCardinality
475
+ },
476
+ identifying: true,
477
+ origin: "inferred"
478
+ };
479
+ }));
480
+ }
481
+ function annotatedERRelations(models) {
482
+ return models.flatMap((model) => (model.documentation ?? "").split("\n").flatMap((line) => {
483
+ const relation = parseRelation(line);
484
+ if (relation === null) return [];
485
+ const [fromCard, toCard] = relation.type.split("-to-");
486
+ if (!isCardinality(fromCard) || !isCardinality(toCard)) return [];
487
+ return [{
488
+ from: {
489
+ model: relation.fromModel,
490
+ field: relation.fromField,
491
+ cardinality: fromCard
492
+ },
493
+ to: {
494
+ model: relation.toModel,
495
+ field: relation.toField,
496
+ cardinality: toCard
497
+ },
498
+ identifying: true,
499
+ origin: "annotated"
500
+ }];
501
+ }));
502
+ }
503
+ function mergeERRelations(models) {
504
+ const inferred = inferredERRelations(models);
505
+ const annotated = annotatedERRelations(models);
506
+ const inferredKeys = new Set(inferred.map(erKey));
507
+ return [...new Map([...inferred.map((r) => [erKey(r), r]), ...annotated.map((r) => [erKey(r), inferredKeys.has(erKey(r)) ? {
508
+ ...r,
509
+ origin: "inferred"
510
+ } : r])]).values()];
511
+ }
512
+ //#endregion
513
+ //#region src/helper/dbml.ts
441
514
  function escapeNote(str) {
442
515
  return str.replace(/'/g, "\\'");
443
516
  }
@@ -553,19 +626,43 @@ function makeRelations$1(models, mapToDbSchema = false) {
553
626
  });
554
627
  }));
555
628
  }
629
+ function isMany(cardinality) {
630
+ return cardinality === "many" || cardinality === "zero-many";
631
+ }
632
+ function dbmlOperator(leftMany, rightMany) {
633
+ if (leftMany && !rightMany) return ">";
634
+ if (!leftMany && rightMany) return "<";
635
+ if (leftMany && rightMany) return "<>";
636
+ return "-";
637
+ }
638
+ function annotatedDbmlRefs(models) {
639
+ const inferredKeys = new Set(inferredERRelations(models).map(erKey));
640
+ return annotatedERRelations(models).filter((relation) => !inferredKeys.has(erKey(relation))).map((relation) => {
641
+ const left = `${relation.to.model}.${relation.to.field}`;
642
+ const right = `${relation.from.model}.${relation.from.field}`;
643
+ const operator = dbmlOperator(isMany(relation.to.cardinality), isMany(relation.from.cardinality));
644
+ return `Ref ${`${relation.to.model}_${relation.to.field}_${relation.from.model}_${relation.from.field}`}: ${left} ${operator} ${right}`;
645
+ });
646
+ }
647
+ //#endregion
648
+ //#region src/generator/dbml.ts
556
649
  function dbmlContent(datamodel, mapToDbSchema = false) {
557
650
  const tables = makeTables(datamodel.models, mapToDbSchema);
558
651
  const enums = makeEnums(datamodel.enums);
559
652
  const refs = makeRelations$1(datamodel.models, mapToDbSchema);
653
+ const logicalRefs = annotatedDbmlRefs(datamodel.models);
560
654
  return [
561
655
  ...enums,
562
656
  ...tables,
563
- ...refs
657
+ ...refs,
658
+ ...logicalRefs
564
659
  ].join("\n\n");
565
660
  }
566
661
  function dbmlToPng(dbml) {
567
662
  return new Resvg(run(dbml, "svg"), { font: { loadSystemFonts: true } }).render().asPng();
568
663
  }
664
+ //#endregion
665
+ //#region src/core/dbml.ts
569
666
  function resolveOutPath(output) {
570
667
  if (path.extname(output)) return output;
571
668
  return path.join(output, "schema.dbml");
@@ -581,9 +678,8 @@ async function dbml(options) {
581
678
  const outPath = resolveOutPath(output);
582
679
  return emitRaw(outPath.endsWith(".png") ? dbmlToPng(content) : content, path.dirname(outPath), outPath);
583
680
  }
584
-
585
681
  //#endregion
586
- //#region src/core/docs/generator/transformDMMF.ts
682
+ //#region src/helper/docs/generator/transformDMMF.ts
587
683
  const getMappings = (mappings, datamodel) => {
588
684
  return mappings.modelOperations.filter((mapping) => {
589
685
  const model = datamodel.models.find((m) => m.name === mapping.model);
@@ -609,9 +705,8 @@ const transformDMMF = (dmmf) => {
609
705
  mappings: getMappings(dmmf.mappings, dmmf.datamodel)
610
706
  };
611
707
  };
612
-
613
708
  //#endregion
614
- //#region src/core/docs/styles.ts
709
+ //#region src/helper/docs/styles.ts
615
710
  const globalCss = css`
616
711
  :-hono-global {
617
712
  :root {
@@ -831,9 +926,8 @@ const mb2Class = css`
831
926
  const ml4Class = css`
832
927
  margin-left: 1rem;
833
928
  `;
834
-
835
929
  //#endregion
836
- //#region src/core/docs/generator/helpers.ts
930
+ //#region src/helper/docs/generator/helpers.ts
837
931
  const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
838
932
  const lowerCase = (name) => name.substring(0, 1).toLowerCase() + name.substring(1);
839
933
  const primitiveTypes = [
@@ -846,9 +940,8 @@ const primitiveTypes = [
846
940
  "Null"
847
941
  ];
848
942
  const isScalarType = (type) => primitiveTypes.includes(type);
849
-
850
943
  //#endregion
851
- //#region src/core/docs/generator/apitypes.tsx
944
+ //#region src/helper/docs/generator/apitypes.tsx
852
945
  const TypeRefLink = ({ typeRef, kind }) => {
853
946
  const typeName = typeRef.type;
854
947
  if (isScalarType(typeName)) return /* @__PURE__ */ jsx(Fragment, { children: typeName });
@@ -946,9 +1039,8 @@ const getTypesData = (d) => {
946
1039
  const createTypes = (d) => {
947
1040
  return /* @__PURE__ */ jsx(TypesSection, { data: getTypesData(d) });
948
1041
  };
949
-
950
1042
  //#endregion
951
- //#region src/core/docs/generator/model.tsx
1043
+ //#region src/helper/docs/generator/model.tsx
952
1044
  const ModelAction = {
953
1045
  create: "create",
954
1046
  deleteMany: "deleteMany",
@@ -1312,9 +1404,8 @@ const getModelData = (d) => ({ models: getModels$1(d) });
1312
1404
  const createModels = (d) => {
1313
1405
  return /* @__PURE__ */ jsx(ModelsSection, { data: getModelData(d) });
1314
1406
  };
1315
-
1316
1407
  //#endregion
1317
- //#region src/core/docs/generator/toc.tsx
1408
+ //#region src/helper/docs/generator/toc.tsx
1318
1409
  const TOCSubHeader = ({ name }) => /* @__PURE__ */ jsx("div", {
1319
1410
  class: tocSubHeaderClass,
1320
1411
  children: /* @__PURE__ */ jsx("a", {
@@ -1442,9 +1533,8 @@ const getTOCData = (d) => ({
1442
1533
  const createTOC = (d) => {
1443
1534
  return /* @__PURE__ */ jsx(TOCComponent, { data: getTOCData(d) });
1444
1535
  };
1445
-
1446
1536
  //#endregion
1447
- //#region src/core/docs/printer/index.tsx
1537
+ //#region src/helper/docs/printer/index.tsx
1448
1538
  const HekirekiLogo = () => /* @__PURE__ */ jsxs("svg", {
1449
1539
  width: "40",
1450
1540
  height: "40",
@@ -1535,7 +1625,11 @@ const generateHTML = (data) => {
1535
1625
  })]
1536
1626
  }) })).toString()}`;
1537
1627
  };
1538
-
1628
+ //#endregion
1629
+ //#region src/generator/docs.ts
1630
+ function docsHTML(dmmf) {
1631
+ return generateHTML(transformDMMF(dmmf));
1632
+ }
1539
1633
  //#endregion
1540
1634
  //#region src/core/docs.ts
1541
1635
  async function docs(options) {
@@ -1544,11 +1638,10 @@ async function docs(options) {
1544
1638
  error: "output is required for Hekireki-Docs. Please specify output in your generator config."
1545
1639
  };
1546
1640
  const output = options.generator.output.value;
1547
- return emitRaw(generateHTML(transformDMMF(options.dmmf)), output, path.join(output, "index.html"));
1641
+ return emitRaw(docsHTML(options.dmmf), output, path.join(output, "index.html"));
1548
1642
  }
1549
-
1550
1643
  //#endregion
1551
- //#region src/core/drizzle.ts
1644
+ //#region src/helper/drizzle.ts
1552
1645
  function resolveDbProvider(provider) {
1553
1646
  switch (provider) {
1554
1647
  case "postgresql":
@@ -1906,6 +1999,8 @@ function makeRelations(models, imports) {
1906
1999
  return `export const ${modelVar}Relations = relations(${modelVar}, ({ ${[needsOne ? "one" : "", needsMany ? "many" : ""].filter(Boolean).join(", ")} }) => ({ ${fieldLines} }))`;
1907
2000
  });
1908
2001
  }
2002
+ //#endregion
2003
+ //#region src/generator/drizzle.ts
1909
2004
  function drizzleSchema(datamodel, provider, indexes) {
1910
2005
  const db = resolveDbProvider(provider);
1911
2006
  const imports = createImports();
@@ -1930,6 +2025,8 @@ function parsePrismaProvider(raw) {
1930
2025
  error: `Unsupported provider: ${raw}`
1931
2026
  };
1932
2027
  }
2028
+ //#endregion
2029
+ //#region src/core/drizzle.ts
1933
2030
  async function drizzle(options) {
1934
2031
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
1935
2032
  ok: false,
@@ -1947,9 +2044,8 @@ async function drizzle(options) {
1947
2044
  };
1948
2045
  return emit(drizzleSchema(options.dmmf.datamodel, providerResult.value, options.dmmf.datamodel.indexes), resolved.dir, resolved.file);
1949
2046
  }
1950
-
1951
2047
  //#endregion
1952
- //#region src/core/ecto.ts
2048
+ //#region src/helper/ecto.ts
1953
2049
  function prismaTypeToEctoType(type) {
1954
2050
  if (type === "Int") return "integer";
1955
2051
  if (type === "BigInt") return "integer";
@@ -2210,12 +2306,16 @@ function ectoSchemas(models, app, allModels, enums) {
2210
2306
  ].join("\n");
2211
2307
  }).filter(Boolean).join("\n\n");
2212
2308
  }
2309
+ //#endregion
2310
+ //#region src/generator/ecto.ts
2213
2311
  function ectoSchemaFiles(models, app, enums) {
2214
2312
  return models.map((model) => ({
2215
2313
  fileName: `${makeSnakeCase(model.name)}.ex`,
2216
2314
  code: ectoSchemas([model], app, models, enums)
2217
2315
  })).filter((entry) => entry.code.trim().length > 0);
2218
2316
  }
2317
+ //#endregion
2318
+ //#region src/core/ecto.ts
2219
2319
  async function ecto(options) {
2220
2320
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
2221
2321
  ok: false,
@@ -2226,11 +2326,10 @@ async function ecto(options) {
2226
2326
  const enums = options.dmmf.datamodel.enums;
2227
2327
  return emitMany(ectoSchemaFiles(options.dmmf.datamodel.models, app, enums), outDir);
2228
2328
  }
2229
-
2230
2329
  //#endregion
2231
- //#region src/core/effect.ts
2330
+ //#region src/helper/effect.ts
2232
2331
  function makeEffectInfer(modelName) {
2233
- return `export type ${modelName}Encoded = typeof ${modelName}Schema.Encoded`;
2332
+ return `export type ${modelName} = typeof ${modelName}Schema.Type`;
2234
2333
  }
2235
2334
  function makeEffectSchema(modelName, fields) {
2236
2335
  return `export const ${modelName}Schema = Schema.Struct({\n${fields}\n})`;
@@ -2261,7 +2360,7 @@ function makeEffectRelations(model, relProps, options) {
2261
2360
  if (relProps.length === 0) return null;
2262
2361
  const base = `...${model.name}Schema.fields,`;
2263
2362
  const rels = relProps.map((r) => `${r.key}:${r.isMany ? `Schema.Array(${r.targetModel}Schema)` : `${r.targetModel}Schema`},`).join("");
2264
- const typeLine = options?.includeType ? `\n\nexport type ${model.name}RelationsEncoded = typeof ${model.name}RelationsSchema.Encoded` : "";
2363
+ const typeLine = options?.includeType ? `\n\nexport type ${model.name}Relations = typeof ${model.name}RelationsSchema.Type` : "";
2265
2364
  return `export const ${model.name}RelationsSchema = Schema.Struct({${base}${rels}})${typeLine}`;
2266
2365
  }
2267
2366
  function effectSchemaCode(models, type, comment, enums) {
@@ -2277,9 +2376,13 @@ function effectSchemaCode(models, type, comment, enums) {
2277
2376
  formatEnum: makeEffectEnumExpression
2278
2377
  });
2279
2378
  }
2379
+ //#endregion
2380
+ //#region src/generator/effect.ts
2280
2381
  function effectCode(dmmf, type, comment, relation) {
2281
2382
  return [effectSchemaCode(dmmf.datamodel.models, type, comment, dmmf.datamodel.enums), relation ? makeRelationsOnly(dmmf, type, makeEffectRelations) : ""].filter(Boolean).join("\n\n");
2282
2383
  }
2384
+ //#endregion
2385
+ //#region src/core/effect.ts
2283
2386
  async function effect(options) {
2284
2387
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
2285
2388
  ok: false,
@@ -2295,9 +2398,8 @@ async function effect(options) {
2295
2398
  };
2296
2399
  return emit(effectCode(options.dmmf, getBool(options.generator.config?.type), getBool(options.generator.config?.comment), getBool(options.generator.config?.relation)), resolved.dir, resolved.file);
2297
2400
  }
2298
-
2299
2401
  //#endregion
2300
- //#region src/core/gorm.ts
2402
+ //#region src/helper/gorm.ts
2301
2403
  const PRISMA_TO_GO = {
2302
2404
  String: "string",
2303
2405
  Int: "int",
@@ -2586,6 +2688,8 @@ function formatImports(imports) {
2586
2688
  ")"
2587
2689
  ];
2588
2690
  }
2691
+ //#endregion
2692
+ //#region src/generator/gorm.ts
2589
2693
  function generateGormModels(models, enums, indexes, packageName = "model") {
2590
2694
  const idx = indexes ?? [];
2591
2695
  const modelBodies = models.map((model) => generateModelStruct(model, models, enums, idx)).filter((body) => body !== null);
@@ -2597,6 +2701,8 @@ function generateGormModels(models, enums, indexes, packageName = "model") {
2597
2701
  ""
2598
2702
  ].join("\n");
2599
2703
  }
2704
+ //#endregion
2705
+ //#region src/core/gorm.ts
2600
2706
  async function gorm(options) {
2601
2707
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
2602
2708
  ok: false,
@@ -2609,18 +2715,17 @@ async function gorm(options) {
2609
2715
  const indexes = options.dmmf.datamodel.indexes;
2610
2716
  return emitRaw(generateGormModels(options.dmmf.datamodel.models, enums, indexes, packageName), path.dirname(outPath), outPath);
2611
2717
  }
2612
-
2613
2718
  //#endregion
2614
- //#region src/core/mermaid-er.ts
2615
- function removeDuplicateRelations(relations) {
2616
- return [...new Set(relations)];
2617
- }
2719
+ //#region src/helper/mermaid-er.ts
2618
2720
  const RELATIONSHIPS = {
2619
2721
  "zero-one": "|o",
2620
2722
  one: "||",
2621
2723
  "zero-many": "}o",
2622
2724
  many: "}|"
2623
2725
  };
2726
+ function erRelationLine(relation) {
2727
+ return ` ${relation.from.model} ${RELATIONSHIPS[relation.from.cardinality]}--${RELATIONSHIPS[relation.to.cardinality]} ${relation.to.model} : "(${relation.from.field}) - (${relation.to.field})"`;
2728
+ }
2624
2729
  function modelFields(model) {
2625
2730
  const fkFields = new Set(model.fields.filter((f) => f.relationFromFields && f.relationFromFields.length > 0).flatMap((f) => f.relationFromFields ?? []));
2626
2731
  return model.fields.map((field) => {
@@ -2638,28 +2743,22 @@ function modelInfo(model) {
2638
2743
  " }"
2639
2744
  ];
2640
2745
  }
2641
- function extractRelationsFromDmmf(models) {
2642
- return models.flatMap((model) => model.fields.filter((field) => field.kind === "object" && field.relationFromFields && field.relationFromFields.length > 0).map((field) => {
2643
- const toModel = model.name;
2644
- const fromModel = field.type;
2645
- const toField = field.relationFromFields?.[0];
2646
- const fromField = field.relationToFields?.[0] ?? "id";
2647
- 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";
2648
- return ` ${fromModel} ${RELATIONSHIPS.one}--${RELATIONSHIPS[toCardinality]} ${toModel} : "(${fromField}) - (${toField})"`;
2649
- }));
2650
- }
2746
+ //#endregion
2747
+ //#region src/generator/mermaid-er.ts
2651
2748
  const ER_HEADER = ["```mermaid", "erDiagram"];
2652
2749
  const ER_FOOTER = ["```"];
2653
2750
  function erContent(models) {
2654
- const uniqueRelations = removeDuplicateRelations(extractRelationsFromDmmf(models));
2751
+ const relations = mergeERRelations(models).map(erRelationLine);
2655
2752
  const modelInfos = models.flatMap(modelInfo);
2656
2753
  return [
2657
2754
  ...ER_HEADER,
2658
- ...uniqueRelations,
2755
+ ...relations,
2659
2756
  ...modelInfos,
2660
2757
  ...ER_FOOTER
2661
2758
  ];
2662
2759
  }
2760
+ //#endregion
2761
+ //#region src/core/mermaid-er.ts
2663
2762
  async function mermaidEr(options) {
2664
2763
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
2665
2764
  ok: false,
@@ -2676,9 +2775,8 @@ async function mermaidEr(options) {
2676
2775
  };
2677
2776
  return emitRaw(content.join("\n"), resolved.dir, resolved.file);
2678
2777
  }
2679
-
2680
2778
  //#endregion
2681
- //#region src/core/sea-orm.ts
2779
+ //#region src/helper/sea-orm.ts
2682
2780
  const PRISMA_TO_RUST = {
2683
2781
  String: "String",
2684
2782
  Int: "i32",
@@ -3034,6 +3132,8 @@ function collectM2MPairs(models) {
3034
3132
  return true;
3035
3133
  });
3036
3134
  }
3135
+ //#endregion
3136
+ //#region src/generator/sea-orm.ts
3037
3137
  function seaOrmFiles(models, enums, serde = {}) {
3038
3138
  const useLines = ["use sea_orm::entity::prelude::*;", "use serde::{Deserialize, Serialize};"];
3039
3139
  const enumFiles = enums.map((e) => ({
@@ -3079,6 +3179,8 @@ function seaOrmFiles(models, enums, serde = {}) {
3079
3179
  code
3080
3180
  })), modEntry];
3081
3181
  }
3182
+ //#endregion
3183
+ //#region src/core/sea-orm.ts
3082
3184
  async function seaOrm(options) {
3083
3185
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
3084
3186
  ok: false,
@@ -3089,9 +3191,8 @@ async function seaOrm(options) {
3089
3191
  const enums = options.dmmf.datamodel.enums;
3090
3192
  return emitMany(seaOrmFiles(options.dmmf.datamodel.models, enums, serde), outDir);
3091
3193
  }
3092
-
3093
3194
  //#endregion
3094
- //#region src/core/sqlalchemy.ts
3195
+ //#region src/helper/sqlalchemy.ts
3095
3196
  const PRISMA_TO_PYTHON = {
3096
3197
  String: "str",
3097
3198
  Int: "int",
@@ -3491,6 +3592,8 @@ function collectGlobalImports(models, _enums, indexes, m2mTables) {
3491
3592
  if (needsUuid) lines.push("import uuid as uuid_mod");
3492
3593
  return lines;
3493
3594
  }
3595
+ //#endregion
3596
+ //#region src/generator/sqlalchemy.ts
3494
3597
  function generateSingleFile(models, enums, indexes) {
3495
3598
  const idx = indexes ?? [];
3496
3599
  const m2mTables = collectManyToManyTables(models);
@@ -3510,6 +3613,8 @@ function generateSingleFile(models, enums, indexes) {
3510
3613
  ""
3511
3614
  ].join("\n");
3512
3615
  }
3616
+ //#endregion
3617
+ //#region src/core/sqlalchemy.ts
3513
3618
  async function sqlalchemy(options) {
3514
3619
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
3515
3620
  ok: false,
@@ -3521,9 +3626,8 @@ async function sqlalchemy(options) {
3521
3626
  const indexes = options.dmmf.datamodel.indexes;
3522
3627
  return emitRaw(generateSingleFile(options.dmmf.datamodel.models, enums, indexes), path.dirname(outPath), outPath);
3523
3628
  }
3524
-
3525
3629
  //#endregion
3526
- //#region src/core/typebox.ts
3630
+ //#region src/helper/typebox.ts
3527
3631
  function makeTypeBoxInfer(modelName) {
3528
3632
  return `export type ${modelName} = Static<typeof ${modelName}Schema>`;
3529
3633
  }
@@ -3576,9 +3680,13 @@ function typeboxSchemaCode(models, type, comment, enums) {
3576
3680
  formatEnum: makeTypeBoxEnumExpression
3577
3681
  });
3578
3682
  }
3683
+ //#endregion
3684
+ //#region src/generator/typebox.ts
3579
3685
  function typeboxCode(dmmf, type, comment, relation) {
3580
3686
  return [typeboxSchemaCode(dmmf.datamodel.models, type, comment, dmmf.datamodel.enums), relation ? makeRelationsOnly(dmmf, type, makeTypeBoxRelations) : ""].filter(Boolean).join("\n\n");
3581
3687
  }
3688
+ //#endregion
3689
+ //#region src/core/typebox.ts
3582
3690
  async function typebox(options) {
3583
3691
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
3584
3692
  ok: false,
@@ -3594,9 +3702,8 @@ async function typebox(options) {
3594
3702
  };
3595
3703
  return emit(typeboxCode(options.dmmf, getBool(options.generator.config?.type), getBool(options.generator.config?.comment), getBool(options.generator.config?.relation)), resolved.dir, resolved.file);
3596
3704
  }
3597
-
3598
3705
  //#endregion
3599
- //#region src/core/valibot.ts
3706
+ //#region src/helper/valibot.ts
3600
3707
  function makeValibotInfer(modelName) {
3601
3708
  return `export type ${modelName} = v.InferOutput<typeof ${modelName}Schema>`;
3602
3709
  }
@@ -3640,9 +3747,13 @@ function valibotSchemaCode(models, type, comment, enums) {
3640
3747
  formatEnum: makeValibotEnumExpression
3641
3748
  });
3642
3749
  }
3750
+ //#endregion
3751
+ //#region src/generator/valibot.ts
3643
3752
  function valibotCode(dmmf, type, comment, relation) {
3644
3753
  return [valibotSchemaCode(dmmf.datamodel.models, type, comment, dmmf.datamodel.enums), relation ? makeRelationsOnly(dmmf, type, makeValibotRelations) : ""].filter(Boolean).join("\n\n");
3645
3754
  }
3755
+ //#endregion
3756
+ //#region src/core/valibot.ts
3646
3757
  async function valibot(options) {
3647
3758
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
3648
3759
  ok: false,
@@ -3658,9 +3769,8 @@ async function valibot(options) {
3658
3769
  };
3659
3770
  return emit(valibotCode(options.dmmf, getBool(options.generator.config?.type), getBool(options.generator.config?.comment), getBool(options.generator.config?.relation)), resolved.dir, resolved.file);
3660
3771
  }
3661
-
3662
3772
  //#endregion
3663
- //#region src/core/zod.ts
3773
+ //#region src/helper/zod.ts
3664
3774
  function makeZodInfer(modelName) {
3665
3775
  return `export type ${modelName} = z.infer<typeof ${modelName}Schema>`;
3666
3776
  }
@@ -3704,9 +3814,13 @@ function zodSchemaCode(models, type, comment, zodVersion, enums) {
3704
3814
  formatEnum: makeZodEnumExpression
3705
3815
  });
3706
3816
  }
3817
+ //#endregion
3818
+ //#region src/generator/zod.ts
3707
3819
  function zodCode(dmmf, type, comment, relation, version) {
3708
3820
  return [zodSchemaCode(dmmf.datamodel.models, type, comment, version, dmmf.datamodel.enums), relation ? makeRelationsOnly(dmmf, type, makeZodRelations) : ""].filter(Boolean).join("\n\n");
3709
3821
  }
3822
+ //#endregion
3823
+ //#region src/core/zod.ts
3710
3824
  async function zod(options) {
3711
3825
  if (!(options.generator.isCustomOutput && options.generator.output?.value)) return {
3712
3826
  ok: false,
@@ -3722,7 +3836,6 @@ async function zod(options) {
3722
3836
  };
3723
3837
  return emit(zodCode(options.dmmf, getBool(options.generator.config?.type), getBool(options.generator.config?.comment), getBool(options.generator.config?.relation), getString(options.generator.config?.zod, "v4")), resolved.dir, resolved.file);
3724
3838
  }
3725
-
3726
3839
  //#endregion
3727
3840
  //#region src/bin/index.ts
3728
3841
  const GENERATORS = {
@@ -3798,6 +3911,5 @@ function registerGenerator(name) {
3798
3911
  }
3799
3912
  });
3800
3913
  }
3801
-
3802
3914
  //#endregion
3803
- export { registerGenerator as t };
3915
+ export { registerGenerator as t };
package/dist/cli/index.js CHANGED
@@ -4,7 +4,6 @@ import path from "node:path";
4
4
  import { serve } from "@hono/node-server";
5
5
  import { serveStatic } from "@hono/node-server/serve-static";
6
6
  import { Hono } from "hono";
7
-
8
7
  //#region src/cli/index.ts
9
8
  const HELP_TEXT = `⚡️ hekireki - Prisma schema tools
10
9
 
@@ -131,6 +130,5 @@ else {
131
130
  console.error(result.error);
132
131
  process.exit(1);
133
132
  }
134
-
135
133
  //#endregion
136
- export { DOCS_HELP_TEXT, HELP_TEXT, handleDocs, hekireki, parsePort };
134
+ export { DOCS_HELP_TEXT, HELP_TEXT, handleDocs, hekireki, parsePort };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hekireki",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "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.",
5
5
  "keywords": [
6
6
  "ajv",
@@ -61,31 +61,31 @@
61
61
  "release": "npm pkg fix && pnpm build && npm publish"
62
62
  },
63
63
  "dependencies": {
64
- "@hono/node-server": "^1.19.9",
65
- "@prisma/generator-helper": "^7.4.0",
64
+ "@hono/node-server": "^2.0.1",
65
+ "@prisma/generator-helper": "^7.8.0",
66
66
  "@resvg/resvg-js": "^2.6.2",
67
67
  "@softwaretechnik/dbml-renderer": "^1.0.31",
68
- "hono": "^4.11.9",
69
- "oxfmt": "^0.33.0"
68
+ "hono": "^4.12.16",
69
+ "oxfmt": "^0.47.0"
70
70
  },
71
71
  "devDependencies": {
72
- "@paralleldrive/cuid2": "^2.3.1",
73
- "@prisma/client": "^7.4.0",
74
- "@sinclair/typebox": "^0.34.33",
75
- "@types/node": "^25.2.3",
76
- "@typescript/native-preview": "7.0.0-dev.20260218.1",
77
- "ajv": "^8.17.1",
78
- "arktype": "^2.1.29",
79
- "better-auth": "^1.5.5",
80
- "drizzle-kit": "^0.31.9",
81
- "drizzle-orm": "^0.45.1",
82
- "effect": "^3.19.18",
72
+ "@paralleldrive/cuid2": "^3.3.0",
73
+ "@prisma/client": "^7.8.0",
74
+ "@sinclair/typebox": "^0.34.49",
75
+ "@types/node": "^25.6.0",
76
+ "@typescript/native-preview": "7.0.0-dev.20260504.1",
77
+ "ajv": "^8.20.0",
78
+ "arktype": "^2.2.0",
79
+ "better-auth": "^1.6.9",
80
+ "drizzle-kit": "^0.31.10",
81
+ "drizzle-orm": "^0.45.2",
82
+ "effect": "^3.21.2",
83
83
  "json-schema-to-ts": "^3.1.1",
84
- "prisma": "^7.4.0",
85
- "tsdown": "^0.20.3",
84
+ "prisma": "^7.8.0",
85
+ "tsdown": "^0.21.10",
86
86
  "tsx": "^4.21.0",
87
- "typescript": "^5.9.3",
88
- "valibot": "1.2.0",
89
- "zod": "^4.3.6"
87
+ "typescript": "^6.0.3",
88
+ "valibot": "1.3.1",
89
+ "zod": "^4.4.3"
90
90
  }
91
91
  }