hekireki 0.4.2 → 0.5.0
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 +180 -20
- package/dist/dbml-content-D3ioOw2D.js +151 -0
- package/dist/fsp-DYtOxLN_.js +68 -0
- package/dist/generator/arktype/index.d.ts +6 -0
- package/dist/generator/arktype/index.js +94 -0
- package/dist/generator/dbml/index.d.ts +6 -0
- package/dist/generator/dbml/index.js +49 -0
- package/dist/generator/ecto/index.d.ts +6 -3
- package/dist/generator/ecto/index.js +150 -13
- package/dist/generator/effect/index.d.ts +6 -0
- package/dist/generator/effect/index.js +94 -0
- package/dist/generator/mermaid-er/index.d.ts +6 -3
- package/dist/generator/mermaid-er/index.js +136 -21
- package/dist/generator/svg/index.d.ts +6 -0
- package/dist/generator/svg/index.js +74 -0
- package/dist/generator/valibot/index.d.ts +6 -3
- package/dist/generator/valibot/index.js +91 -45
- package/dist/generator/zod/index.d.ts +6 -3
- package/dist/generator/zod/index.js +98 -56
- package/dist/relations-CxeKj9KD.js +12 -0
- package/dist/utils-CXBzdZih.js +134 -0
- package/package.json +29 -17
- package/dist/generator/ecto/generator/ecto.d.ts +0 -3
- package/dist/generator/ecto/generator/ecto.js +0 -131
- package/dist/generator/ecto/utils/index.d.ts +0 -1
- package/dist/generator/ecto/utils/index.js +0 -1
- package/dist/generator/ecto/utils/prisma-type-to-ecto-type.d.ts +0 -1
- package/dist/generator/ecto/utils/prisma-type-to-ecto-type.js +0 -11
- package/dist/generator/mermaid-er/generator/er-content.d.ts +0 -8
- package/dist/generator/mermaid-er/generator/er-content.js +0 -23
- package/dist/generator/mermaid-er/generator/index.d.ts +0 -4
- package/dist/generator/mermaid-er/generator/index.js +0 -4
- package/dist/generator/mermaid-er/generator/model-fields.d.ts +0 -8
- package/dist/generator/mermaid-er/generator/model-fields.js +0 -25
- package/dist/generator/mermaid-er/generator/model-info.d.ts +0 -8
- package/dist/generator/mermaid-er/generator/model-info.js +0 -10
- package/dist/generator/mermaid-er/generator/relation-line.d.ts +0 -13
- package/dist/generator/mermaid-er/generator/relation-line.js +0 -14
- package/dist/generator/mermaid-er/helper/build-relation-line.d.ts +0 -9
- package/dist/generator/mermaid-er/helper/build-relation-line.js +0 -37
- package/dist/generator/mermaid-er/helper/extract-relations.d.ts +0 -8
- package/dist/generator/mermaid-er/helper/extract-relations.js +0 -22
- package/dist/generator/mermaid-er/utils/index.d.ts +0 -34
- package/dist/generator/mermaid-er/utils/index.js +0 -48
- package/dist/generator/valibot/generator/index.d.ts +0 -3
- package/dist/generator/valibot/generator/index.js +0 -3
- package/dist/generator/valibot/generator/schema.d.ts +0 -16
- package/dist/generator/valibot/generator/schema.js +0 -51
- package/dist/generator/valibot/generator/schemas.d.ts +0 -13
- package/dist/generator/valibot/generator/schemas.js +0 -17
- package/dist/generator/valibot/generator/valibot.d.ts +0 -9
- package/dist/generator/valibot/generator/valibot.js +0 -42
- package/dist/generator/valibot/utils/index.d.ts +0 -44
- package/dist/generator/valibot/utils/index.js +0 -75
- package/dist/generator/zod/generator/index.d.ts +0 -3
- package/dist/generator/zod/generator/index.js +0 -3
- package/dist/generator/zod/generator/schema.d.ts +0 -17
- package/dist/generator/zod/generator/schema.js +0 -38
- package/dist/generator/zod/generator/schemas.d.ts +0 -13
- package/dist/generator/zod/generator/schemas.js +0 -17
- package/dist/generator/zod/generator/zod.d.ts +0 -9
- package/dist/generator/zod/generator/zod.js +0 -47
- package/dist/generator/zod/utils/index.d.ts +0 -46
- package/dist/generator/zod/utils/index.js +0 -75
- package/dist/shared/format/index.d.ts +0 -1
- package/dist/shared/format/index.js +0 -9
- package/dist/shared/generator/index.d.ts +0 -11
- package/dist/shared/generator/index.js +0 -23
- package/dist/shared/helper/relations.d.ts +0 -7
- package/dist/shared/helper/relations.js +0 -5
- package/dist/shared/utils/index.d.ts +0 -61
- package/dist/shared/utils/index.js +0 -63
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { GeneratorOptions } from "@prisma/generator-helper";
|
|
2
|
+
|
|
3
|
+
//#region src/generator/ecto/index.d.ts
|
|
4
|
+
declare function main(options: GeneratorOptions): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { main };
|
|
@@ -1,18 +1,155 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import { n as writeFile, t as mkdir } from "../../fsp-DYtOxLN_.js";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import pkg from "@prisma/generator-helper";
|
|
5
|
+
import { makeSnakeCase } from "utils-lab";
|
|
6
|
+
|
|
7
|
+
//#region src/generator/ecto/utils/prisma-type-to-ecto-type.ts
|
|
8
|
+
function prismaTypeToEctoType(type) {
|
|
9
|
+
if (type === "Int") return "integer";
|
|
10
|
+
if (type === "String") return "string";
|
|
11
|
+
if (type === "Boolean") return "boolean";
|
|
12
|
+
if (type === "DateTime") return "utc_datetime";
|
|
13
|
+
return "string";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/generator/ecto/generator/ecto.ts
|
|
18
|
+
function getPrimaryKeyConfig(field) {
|
|
19
|
+
if (field.type === "String" && field.default && typeof field.default === "object" && "name" in field.default && field.default.name === "uuid") return {
|
|
20
|
+
line: "@primary_key {:id, :binary_id, autogenerate: true}",
|
|
21
|
+
typeSpec: "Ecto.UUID.t()",
|
|
22
|
+
omitIdFieldInSchema: true
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
line: "@primary_key false",
|
|
26
|
+
typeSpec: "String.t()",
|
|
27
|
+
omitIdFieldInSchema: false
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function getFieldDefaultOption(field) {
|
|
31
|
+
const def = field.default;
|
|
32
|
+
if (def === void 0 || def === null) return null;
|
|
33
|
+
if (typeof def === "string") return `default: "${def}"`;
|
|
34
|
+
if (typeof def === "number" || typeof def === "boolean") return `default: ${def}`;
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function ectoTypeToTypespec(type) {
|
|
38
|
+
switch (type) {
|
|
39
|
+
case "string": return "String.t()";
|
|
40
|
+
case "integer": return "integer()";
|
|
41
|
+
case "float": return "float()";
|
|
42
|
+
case "boolean": return "boolean()";
|
|
43
|
+
case "binary_id": return "Ecto.UUID.t()";
|
|
44
|
+
case "naive_datetime": return "NaiveDateTime.t()";
|
|
45
|
+
case "utc_datetime": return "DateTime.t()";
|
|
46
|
+
default: return "term()";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function buildTimestampsLine(fields) {
|
|
50
|
+
const insertedAliases = [
|
|
51
|
+
"inserted_at",
|
|
52
|
+
"created_at",
|
|
53
|
+
"createdAt"
|
|
54
|
+
];
|
|
55
|
+
const updatedAliases = [
|
|
56
|
+
"updated_at",
|
|
57
|
+
"modified_at",
|
|
58
|
+
"updatedAt",
|
|
59
|
+
"modifiedAt"
|
|
60
|
+
];
|
|
61
|
+
const inserted = fields.find((f) => insertedAliases.includes(f.name));
|
|
62
|
+
const updated = fields.find((f) => updatedAliases.includes(f.name));
|
|
63
|
+
const exclude = /* @__PURE__ */ new Set();
|
|
64
|
+
if (inserted) exclude.add(inserted.name);
|
|
65
|
+
if (updated) exclude.add(updated.name);
|
|
66
|
+
if (!(inserted || updated)) return {
|
|
67
|
+
line: null,
|
|
68
|
+
exclude
|
|
69
|
+
};
|
|
70
|
+
if (inserted?.name === "inserted_at" && updated?.name === "updated_at") return {
|
|
71
|
+
line: " timestamps()",
|
|
72
|
+
exclude
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
line: ` timestamps(inserted_at: :${inserted?.name ?? "inserted_at"}, updated_at: :${updated?.name ?? "updated_at"})`,
|
|
76
|
+
exclude
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function ectoSchemas(models, app) {
|
|
80
|
+
return models.map((model) => {
|
|
81
|
+
const idField = model.fields.find((f) => f.isId);
|
|
82
|
+
if (!idField) return "";
|
|
83
|
+
const pk = getPrimaryKeyConfig(idField);
|
|
84
|
+
const fields = model.fields.map((f) => ({ ...f }));
|
|
85
|
+
const { line: timestampsLine, exclude: timestampsExclude } = buildTimestampsLine(fields);
|
|
86
|
+
const schemaFieldsRaw = fields.filter((f) => !(f.relationName || f.isId && pk.omitIdFieldInSchema || timestampsExclude.has(f.name)));
|
|
87
|
+
const typeSpecFields = [`id: ${pk.typeSpec}`, ...schemaFieldsRaw.map((f) => `${f.name}: ${ectoTypeToTypespec(prismaTypeToEctoType(f.type))}`)];
|
|
88
|
+
const typeSpecLines = [
|
|
89
|
+
" @type t :: %__MODULE__{",
|
|
90
|
+
...typeSpecFields.map((line, i) => {
|
|
91
|
+
return ` ${line}${i === typeSpecFields.length - 1 ? "" : ","}`;
|
|
92
|
+
}),
|
|
93
|
+
" }"
|
|
94
|
+
];
|
|
95
|
+
const schemaFields = schemaFieldsRaw.map((f) => {
|
|
96
|
+
const type = f.isId ? "binary_id" : prismaTypeToEctoType(f.type);
|
|
97
|
+
const primary = f.isId && !pk.omitIdFieldInSchema ? ", primary_key: true" : "";
|
|
98
|
+
const defaultOpt = getFieldDefaultOption(f);
|
|
99
|
+
const defaultClause = defaultOpt ? `, ${defaultOpt}` : "";
|
|
100
|
+
return ` field(:${f.name}, :${type}${primary}${defaultClause})`;
|
|
101
|
+
});
|
|
102
|
+
return [
|
|
103
|
+
`defmodule ${app}.${model.name} do`,
|
|
104
|
+
" use Ecto.Schema",
|
|
105
|
+
"",
|
|
106
|
+
` ${pk.line}`,
|
|
107
|
+
"",
|
|
108
|
+
...typeSpecLines,
|
|
109
|
+
"",
|
|
110
|
+
` schema "${makeSnakeCase(model.name)}" do`,
|
|
111
|
+
...schemaFields,
|
|
112
|
+
...timestampsLine ? [timestampsLine] : [],
|
|
113
|
+
" end",
|
|
114
|
+
"end"
|
|
115
|
+
].join("\n");
|
|
116
|
+
}).filter(Boolean).join("\n\n");
|
|
117
|
+
}
|
|
118
|
+
async function writeEctoSchemasToFiles(models, app, outDir) {
|
|
119
|
+
const mkdirResult = await mkdir(outDir);
|
|
120
|
+
if (!mkdirResult.ok) return mkdirResult;
|
|
121
|
+
for (const model of models) {
|
|
122
|
+
const code = ectoSchemas([model], app);
|
|
123
|
+
if (!code.trim()) continue;
|
|
124
|
+
const filePath = join(outDir, `${makeSnakeCase(model.name)}.ex`);
|
|
125
|
+
const writeResult = await writeFile(filePath, code);
|
|
126
|
+
if (!writeResult.ok) return writeResult;
|
|
127
|
+
console.log(`✅ wrote ${filePath}`);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
value: void 0
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/generator/ecto/index.ts
|
|
4
137
|
const { generatorHandler } = pkg;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
138
|
+
async function main(options) {
|
|
139
|
+
const output = options.generator.output?.value ?? "./ecto";
|
|
140
|
+
const app = options.generator.config?.app ?? "MyApp";
|
|
141
|
+
const result = await writeEctoSchemasToFiles(options.dmmf.datamodel.models, app, output);
|
|
142
|
+
if (!result.ok) throw new Error(`Failed to write Ecto schemas: ${result.error}`);
|
|
9
143
|
}
|
|
10
144
|
generatorHandler({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
145
|
+
onManifest() {
|
|
146
|
+
return {
|
|
147
|
+
defaultOutput: "./ecto/",
|
|
148
|
+
prettyName: "Hekireki-Ecto"
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
onGenerate: main
|
|
18
152
|
});
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
export { main };
|
|
@@ -0,0 +1,94 @@
|
|
|
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";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import pkg from "@prisma/generator-helper";
|
|
6
|
+
import { makeValidationExtractor } from "utils-lab";
|
|
7
|
+
|
|
8
|
+
//#region src/generator/effect/generator/schema.ts
|
|
9
|
+
/**
|
|
10
|
+
* Generate Effect Schema
|
|
11
|
+
* @param modelName - The name of the model
|
|
12
|
+
* @param fields - The fields of the model
|
|
13
|
+
* @returns The generated Effect Schema
|
|
14
|
+
*/
|
|
15
|
+
function schema(modelName, fields) {
|
|
16
|
+
return `export const ${modelName}Schema = Schema.Struct({\n${fields}\n})`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/generator/effect/generator/schemas.ts
|
|
21
|
+
/**
|
|
22
|
+
* Generate properties for Effect Schema.
|
|
23
|
+
*/
|
|
24
|
+
function effectPropertiesGenerator(fields, comment) {
|
|
25
|
+
return fields.map((field) => {
|
|
26
|
+
return `${comment && field.comment.length > 0 ? `${field.comment.map((c) => ` /** ${c} */`).join("\n")}\n` : ""} ${field.fieldName}: ${field.validation ?? "Schema.Unknown"},`;
|
|
27
|
+
}).join("\n");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates Effect schemas from model fields.
|
|
31
|
+
*
|
|
32
|
+
* @param modelFields - The fields of the model
|
|
33
|
+
* @param comment - Whether to include comments in the generated code
|
|
34
|
+
* @returns The generated Effect schemas
|
|
35
|
+
*/
|
|
36
|
+
function schemas(modelFields, comment) {
|
|
37
|
+
return schemaFromFields(modelFields, comment, schema, effectPropertiesGenerator);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/generator/effect/generator/effect.ts
|
|
42
|
+
/**
|
|
43
|
+
* Generate Effect Schema infer type statement.
|
|
44
|
+
* @param modelName - The name of the model
|
|
45
|
+
* @returns The generated type inference statement
|
|
46
|
+
*/
|
|
47
|
+
function makeEffectInfer(modelName) {
|
|
48
|
+
return `export type ${modelName} = Schema.Schema.Type<typeof ${modelName}Schema>`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates Effect schemas and types from models.
|
|
52
|
+
*
|
|
53
|
+
* @param models - The models to generate the Effect schemas and types for
|
|
54
|
+
* @param type - Whether to generate types
|
|
55
|
+
* @param comment - Whether to include comments in the generated code
|
|
56
|
+
* @returns The generated Effect schemas and types
|
|
57
|
+
*/
|
|
58
|
+
function effect(models, type, comment) {
|
|
59
|
+
return validationSchemas(models, type, comment, {
|
|
60
|
+
importStatement: `import { Schema } from 'effect'`,
|
|
61
|
+
annotationPrefix: "@e.",
|
|
62
|
+
parseDocument: parseDocumentWithoutAnnotations,
|
|
63
|
+
extractValidation: makeValidationExtractor("@e."),
|
|
64
|
+
inferType: makeEffectInfer,
|
|
65
|
+
schemas
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/generator/effect/index.ts
|
|
71
|
+
const { generatorHandler } = pkg;
|
|
72
|
+
const emit = async (options) => {
|
|
73
|
+
const outDir = options.generator.output?.value ?? "./effect";
|
|
74
|
+
const file = getString(options.generator.config?.file, "index.ts") ?? "index.ts";
|
|
75
|
+
const fmtResult = await fmt(effect(options.dmmf.datamodel.models, getBool(options.generator.config?.type), getBool(options.generator.config?.comment)));
|
|
76
|
+
if (!fmtResult.ok) throw new Error(`Format error: ${fmtResult.error}`);
|
|
77
|
+
const mkdirResult = await mkdir(outDir);
|
|
78
|
+
if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
|
|
79
|
+
const writeResult = await writeFile(path.join(outDir, file), fmtResult.value);
|
|
80
|
+
if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
|
|
81
|
+
};
|
|
82
|
+
const onGenerate = (options) => emit(options);
|
|
83
|
+
generatorHandler({
|
|
84
|
+
onManifest() {
|
|
85
|
+
return {
|
|
86
|
+
defaultOutput: "./effect/",
|
|
87
|
+
prettyName: "Hekireki-Effect"
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
onGenerate
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { onGenerate };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { GeneratorOptions } from "@prisma/generator-helper";
|
|
2
|
+
|
|
3
|
+
//#region src/generator/mermaid-er/index.d.ts
|
|
4
|
+
declare function main(options: GeneratorOptions): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { main };
|
|
@@ -1,26 +1,141 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import pkg from
|
|
4
|
-
|
|
2
|
+
import { n as writeFile, t as mkdir } from "../../fsp-DYtOxLN_.js";
|
|
3
|
+
import pkg from "@prisma/generator-helper";
|
|
4
|
+
|
|
5
|
+
//#region src/generator/mermaid-er/helper/extract-relations-from-dmmf.ts
|
|
6
|
+
const RELATIONSHIPS = {
|
|
7
|
+
"zero-one": "|o",
|
|
8
|
+
one: "||",
|
|
9
|
+
"zero-many": "}o",
|
|
10
|
+
many: "}|"
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Extract Mermaid ER diagram relation lines from Prisma DMMF models.
|
|
14
|
+
* This function automatically detects relations from field definitions.
|
|
15
|
+
*
|
|
16
|
+
* @param models - The list of Prisma DMMF models.
|
|
17
|
+
* @returns An array of Mermaid ER diagram relation lines.
|
|
18
|
+
*/
|
|
19
|
+
function extractRelationsFromDmmf(models) {
|
|
20
|
+
const relations = [];
|
|
21
|
+
for (const model of models) for (const field of model.fields) {
|
|
22
|
+
if (field.kind !== "object" || !field.relationFromFields || field.relationFromFields.length === 0) continue;
|
|
23
|
+
const toModel = model.name;
|
|
24
|
+
const fromModel = field.type;
|
|
25
|
+
const toField = field.relationFromFields[0];
|
|
26
|
+
const fromField = field.relationToFields?.[0] ?? "id";
|
|
27
|
+
const fromCardinality = "one";
|
|
28
|
+
const inverseField = models.find((m) => m.name === fromModel)?.fields.find((f) => f.relationName === field.relationName && f.name !== field.name);
|
|
29
|
+
let toCardinality;
|
|
30
|
+
if (inverseField?.isList) toCardinality = field.isRequired ? "many" : "zero-many";
|
|
31
|
+
else toCardinality = field.isRequired ? "one" : "zero-one";
|
|
32
|
+
const relationLine = ` ${fromModel} ${RELATIONSHIPS[fromCardinality]}--${RELATIONSHIPS[toCardinality]} ${toModel} : "(${fromField}) - (${toField})"`;
|
|
33
|
+
relations.push(relationLine);
|
|
34
|
+
}
|
|
35
|
+
return relations;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/generator/mermaid-er/utils/index.ts
|
|
40
|
+
/**
|
|
41
|
+
* Remove duplicate relation lines from an array of Mermaid ER diagram relations.
|
|
42
|
+
*
|
|
43
|
+
* @param relations - An array of relation lines (e.g., generated from `relationLine`).
|
|
44
|
+
* @returns A new array with duplicates removed, preserving insertion order.
|
|
45
|
+
*/
|
|
46
|
+
function removeDuplicateRelations(relations) {
|
|
47
|
+
return [...new Set(relations)];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/generator/mermaid-er/generator/model-fields.ts
|
|
52
|
+
const ZOD_ANNOTATION = "@z.";
|
|
53
|
+
const VALIBOT_ANNOTATION = "@v.";
|
|
54
|
+
const RELATION_ANNOTATION = "@relation";
|
|
55
|
+
/**
|
|
56
|
+
* Convert Prisma type to lowercase Mermaid ER type.
|
|
57
|
+
*/
|
|
58
|
+
function toMermaidType(prismaType) {
|
|
59
|
+
return prismaType.toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generate Mermaid ER field lines from a Prisma model.
|
|
63
|
+
*
|
|
64
|
+
* @param model - A Prisma DMMF model definition.
|
|
65
|
+
* @returns An array of strings representing each field in Mermaid ER syntax, excluding relation fields and annotations.
|
|
66
|
+
*/
|
|
67
|
+
function modelFields(model) {
|
|
68
|
+
const fkFields = new Set(model.fields.filter((f) => f.relationFromFields && f.relationFromFields.length > 0).flatMap((f) => f.relationFromFields ?? []));
|
|
69
|
+
return model.fields.map((field) => {
|
|
70
|
+
if (field.relationName) return null;
|
|
71
|
+
const commentPart = field.documentation ? field.documentation.split("\n").filter((line) => !(line.includes(ZOD_ANNOTATION) || line.includes(VALIBOT_ANNOTATION) || line.includes(RELATION_ANNOTATION))).join("\n").trim() : "";
|
|
72
|
+
const keyMarker = field.isId ? "PK" : fkFields.has(field.name) ? "FK" : "";
|
|
73
|
+
const keyPart = keyMarker ? ` ${keyMarker}` : "";
|
|
74
|
+
return ` ${toMermaidType(field.type)} ${field.name}${keyPart}${commentPart ? ` "${commentPart}"` : ""}`;
|
|
75
|
+
}).filter((field) => field !== null);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/generator/mermaid-er/generator/model-info.ts
|
|
80
|
+
/**
|
|
81
|
+
* Generate Mermaid ER diagram model block from a Prisma model.
|
|
82
|
+
*
|
|
83
|
+
* @param model - A Prisma DMMF model definition.
|
|
84
|
+
* @returns An array of strings representing the model block in Mermaid ER syntax.
|
|
85
|
+
*/
|
|
86
|
+
function modelInfo(model) {
|
|
87
|
+
return [
|
|
88
|
+
` ${model.name} {`,
|
|
89
|
+
...modelFields(model),
|
|
90
|
+
" }"
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/generator/mermaid-er/generator/er-content.ts
|
|
96
|
+
const ER_HEADER = ["```mermaid", "erDiagram"];
|
|
97
|
+
const ER_FOOTER = ["```"];
|
|
98
|
+
/**
|
|
99
|
+
* Generate Mermaid ER diagram content from Prisma models.
|
|
100
|
+
*
|
|
101
|
+
* @param models - The list of Prisma DMMF models.
|
|
102
|
+
* @returns An array of Mermaid ER diagram lines.
|
|
103
|
+
*/
|
|
104
|
+
function erContent(models) {
|
|
105
|
+
const uniqueRelations = removeDuplicateRelations(extractRelationsFromDmmf(models));
|
|
106
|
+
const modelInfos = models.flatMap(modelInfo);
|
|
107
|
+
return [
|
|
108
|
+
...ER_HEADER,
|
|
109
|
+
...uniqueRelations,
|
|
110
|
+
...modelInfos,
|
|
111
|
+
...ER_FOOTER
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/generator/mermaid-er/index.ts
|
|
5
117
|
const { generatorHandler } = pkg;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
118
|
+
async function main(options) {
|
|
119
|
+
const content = erContent(options.dmmf.datamodel.models);
|
|
120
|
+
const output = options.generator.output?.value ?? "./mermaid-er";
|
|
121
|
+
const file = options.generator.config?.file ?? "ER.md";
|
|
122
|
+
const isOutputFile = output.includes(".");
|
|
123
|
+
const outputDir = isOutputFile ? "." : output;
|
|
124
|
+
const outputFile = isOutputFile ? output : `${output}/${file}`;
|
|
125
|
+
const mkdirResult = await mkdir(outputDir);
|
|
126
|
+
if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
|
|
127
|
+
const writeResult = await writeFile(outputFile, content.join("\n"));
|
|
128
|
+
if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
|
|
16
129
|
}
|
|
17
|
-
// prisma generator handler
|
|
18
130
|
generatorHandler({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
131
|
+
onManifest() {
|
|
132
|
+
return {
|
|
133
|
+
defaultOutput: "./mermaid-er",
|
|
134
|
+
prettyName: "Hekireki-ER"
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
onGenerate: main
|
|
26
138
|
});
|
|
139
|
+
|
|
140
|
+
//#endregion
|
|
141
|
+
export { main };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as writeFile, r as writeFileBinary, t as mkdir } from "../../fsp-DYtOxLN_.js";
|
|
3
|
+
import { t as dbmlContent } from "../../dbml-content-D3ioOw2D.js";
|
|
4
|
+
import pkg from "@prisma/generator-helper";
|
|
5
|
+
import { Resvg } from "@resvg/resvg-js";
|
|
6
|
+
import { run } from "@softwaretechnik/dbml-renderer";
|
|
7
|
+
|
|
8
|
+
//#region src/generator/svg/index.ts
|
|
9
|
+
const { generatorHandler } = pkg;
|
|
10
|
+
/**
|
|
11
|
+
* Get string value from config
|
|
12
|
+
*/
|
|
13
|
+
function getStringValue(value) {
|
|
14
|
+
if (value === void 0) return void 0;
|
|
15
|
+
return Array.isArray(value) ? value[0] : value;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get boolean option from config
|
|
19
|
+
*/
|
|
20
|
+
function getBoolOption(config, key, defaultValue) {
|
|
21
|
+
const value = getStringValue(config[key]);
|
|
22
|
+
if (value === void 0) return defaultValue;
|
|
23
|
+
return value.toLowerCase() !== "false";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get file extension for format
|
|
27
|
+
*/
|
|
28
|
+
function getExtension(format) {
|
|
29
|
+
switch (format) {
|
|
30
|
+
case "png": return "png";
|
|
31
|
+
case "dot": return "dot";
|
|
32
|
+
default: return "svg";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function main(options) {
|
|
36
|
+
const { config } = options.generator;
|
|
37
|
+
const mapToDbSchema = getBoolOption(config, "mapToDbSchema", false);
|
|
38
|
+
const includeRelationFields = getBoolOption(config, "includeRelationFields", false);
|
|
39
|
+
const format = getStringValue(config.format)?.toLowerCase() || "png";
|
|
40
|
+
const dbml = dbmlContent(options.dmmf.datamodel, mapToDbSchema, includeRelationFields);
|
|
41
|
+
const output = options.generator.output?.value ?? "./docs";
|
|
42
|
+
const defaultFile = `er-diagram.${getExtension(format)}`;
|
|
43
|
+
const file = getStringValue(config.file) ?? defaultFile;
|
|
44
|
+
const isOutputFile = output.includes(".");
|
|
45
|
+
const outputDir = isOutputFile ? "." : output;
|
|
46
|
+
const outputFile = isOutputFile ? output : `${output}/${file}`;
|
|
47
|
+
const mkdirResult = await mkdir(outputDir);
|
|
48
|
+
if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
|
|
49
|
+
if (format === "dot") {
|
|
50
|
+
const writeResult = await writeFile(outputFile, run(dbml, "dot"));
|
|
51
|
+
if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
|
|
52
|
+
} else {
|
|
53
|
+
const svg = run(dbml, "svg");
|
|
54
|
+
if (format === "png") {
|
|
55
|
+
const writeResult = await writeFileBinary(outputFile, new Resvg(svg, { font: { loadSystemFonts: true } }).render().asPng());
|
|
56
|
+
if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
|
|
57
|
+
} else {
|
|
58
|
+
const writeResult = await writeFile(outputFile, svg);
|
|
59
|
+
if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
generatorHandler({
|
|
64
|
+
onManifest() {
|
|
65
|
+
return {
|
|
66
|
+
defaultOutput: "./docs",
|
|
67
|
+
prettyName: "Hekireki-SVG"
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
onGenerate: main
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { main };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { GeneratorOptions } from "@prisma/generator-helper";
|
|
2
|
+
|
|
3
|
+
//#region src/generator/valibot/index.d.ts
|
|
4
|
+
declare const onGenerate: (options: GeneratorOptions) => Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { onGenerate };
|