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 +68 -28
- package/dist/cli/index.js +1 -1
- package/dist/generator/arktype/index.d.ts +2 -2
- package/dist/generator/arktype/index.js +10 -81
- package/dist/generator/dbml/index.d.ts +1 -4
- package/dist/generator/dbml/index.js +77 -143
- package/dist/generator/docs/index.js +298 -428
- package/dist/generator/ecto/index.js +5 -28
- package/dist/generator/effect/index.d.ts +2 -2
- package/dist/generator/effect/index.js +10 -81
- package/dist/generator/mermaid-er/index.js +16 -72
- package/dist/generator/valibot/index.d.ts +2 -2
- package/dist/generator/valibot/index.js +11 -59
- package/dist/generator/zod/index.d.ts +2 -2
- package/dist/generator/zod/index.js +11 -65
- package/dist/prisma-vPHUnYiY.js +72 -0
- package/dist/utils-0nIzqFtt.js +311 -0
- package/package.json +16 -16
- package/dist/fsp-FOfmtPYd.js +0 -68
- package/dist/utils-BHGDO4qk.js +0 -145
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+

|
|
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 = "
|
|
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-
|
|
73
|
-
provider = "hekireki-
|
|
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
|
-
##
|
|
128
|
+
## Generated Output
|
|
126
129
|
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
294
|
+
### Mermaid
|
|
292
295
|
|
|
293
296
|
```mermaid
|
|
294
297
|
erDiagram
|
|
@@ -305,7 +308,7 @@ erDiagram
|
|
|
305
308
|
}
|
|
306
309
|
```
|
|
307
310
|
|
|
308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
371
|
+
### PNG
|
|
369
372
|
|
|
370
|
-
The `hekireki-
|
|
373
|
+
The `hekireki-dbml` generator also outputs ER diagrams as PNG images when the `output` path ends with `.png`:
|
|
371
374
|
|
|
372
|
-
|
|
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 = "
|
|
436
|
-
|
|
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
|
-
//
|
|
440
|
-
generator Hekireki-
|
|
441
|
-
provider = "hekireki-
|
|
442
|
-
output = "./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
|
|
4
|
+
declare function main(options: GeneratorOptions): Promise<void>;
|
|
5
5
|
//#endregion
|
|
6
|
-
export {
|
|
6
|
+
export { main };
|
|
@@ -1,73 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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/
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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 {
|
|
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/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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:
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
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 =
|
|
198
|
-
const
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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 {
|