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
package/README.md
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# Hekireki
|
|
2
2
|
|
|
3
|
-
**[Hekireki](https://www.npmjs.com/package/hekireki)** is a tool that generates validation schemas for Zod and
|
|
3
|
+
**[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.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- ๐ Automatically generates [Zod](https://zod.dev/) schemas from your Prisma schema
|
|
8
8
|
- ๐ค Automatically generates [Valibot](https://valibot.dev/) schemas from your Prisma schema
|
|
9
|
-
-
|
|
9
|
+
- ๐น Automatically generates [ArkType](https://arktype.io/) schemas from your Prisma schema
|
|
10
|
+
- โก Automatically generates [Effect Schema](https://effect.website/docs/schema/introduction/) from your Prisma schema
|
|
11
|
+
- ๐ 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)
|
|
10
14
|
- ๐งช Generates [Ecto](https://hexdocs.pm/ecto/Ecto.Schema.html) schemas for Elixir projects
|
|
11
15
|
โ ๏ธ Foreign key constraints are **not** included โ manage relationships in your application logic
|
|
12
16
|
|
|
@@ -21,13 +25,8 @@ npm install -D hekireki
|
|
|
21
25
|
Prepare `schema.prisma`:
|
|
22
26
|
|
|
23
27
|
```prisma
|
|
24
|
-
generator client {
|
|
25
|
-
provider = "prisma-client-js"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
28
|
datasource db {
|
|
29
29
|
provider = "sqlite"
|
|
30
|
-
url = env("DATABASE_URL")
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
generator Hekireki-ER {
|
|
@@ -48,42 +47,75 @@ generator Hekireki-Valibot {
|
|
|
48
47
|
relation = true
|
|
49
48
|
}
|
|
50
49
|
|
|
50
|
+
generator Hekireki-ArkType {
|
|
51
|
+
provider = "hekireki-arktype"
|
|
52
|
+
type = true
|
|
53
|
+
comment = true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
generator Hekireki-Effect {
|
|
57
|
+
provider = "hekireki-effect"
|
|
58
|
+
type = true
|
|
59
|
+
comment = true
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
generator Hekireki-Ecto {
|
|
52
63
|
provider = "hekireki-ecto"
|
|
53
64
|
output = "schema"
|
|
54
65
|
app = "DBSchema"
|
|
55
66
|
}
|
|
56
67
|
|
|
68
|
+
generator Hekireki-DBML {
|
|
69
|
+
provider = "hekireki-dbml"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
generator Hekireki-SVG {
|
|
73
|
+
provider = "hekireki-svg"
|
|
74
|
+
output = "docs"
|
|
75
|
+
format = "png"
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
model User {
|
|
58
79
|
/// Primary key
|
|
59
80
|
/// @z.uuid()
|
|
60
81
|
/// @v.pipe(v.string(), v.uuid())
|
|
82
|
+
/// @a."string.uuid"
|
|
83
|
+
/// @e.Schema.UUID
|
|
61
84
|
id String @id @default(uuid())
|
|
62
85
|
/// Display name
|
|
63
86
|
/// @z.string().min(1).max(50)
|
|
64
87
|
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(50))
|
|
88
|
+
/// @a."1 <= string <= 50"
|
|
89
|
+
/// @e.Schema.String.pipe(Schema.minLength(1), Schema.maxLength(50))
|
|
65
90
|
name String
|
|
66
91
|
/// One-to-many relation to Post
|
|
67
92
|
posts Post[]
|
|
68
93
|
}
|
|
69
94
|
|
|
70
|
-
/// @relation User.id Post.userId one-to-many
|
|
71
95
|
model Post {
|
|
72
96
|
/// Primary key
|
|
73
97
|
/// @z.uuid()
|
|
74
98
|
/// @v.pipe(v.string(), v.uuid())
|
|
99
|
+
/// @a."string.uuid"
|
|
100
|
+
/// @e.Schema.UUID
|
|
75
101
|
id String @id @default(uuid())
|
|
76
102
|
/// Article title
|
|
77
103
|
/// @z.string().min(1).max(100)
|
|
78
104
|
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(100))
|
|
105
|
+
/// @a."1 <= string <= 100"
|
|
106
|
+
/// @e.Schema.String.pipe(Schema.minLength(1), Schema.maxLength(100))
|
|
79
107
|
title String
|
|
80
108
|
/// Body content (no length limit)
|
|
81
109
|
/// @z.string()
|
|
82
110
|
/// @v.string()
|
|
111
|
+
/// @a."string"
|
|
112
|
+
/// @e.Schema.String
|
|
83
113
|
content String
|
|
84
114
|
/// Foreign key referencing User.id
|
|
85
115
|
/// @z.uuid()
|
|
86
116
|
/// @v.pipe(v.string(), v.uuid())
|
|
117
|
+
/// @a."string.uuid"
|
|
118
|
+
/// @e.Schema.UUID
|
|
87
119
|
userId String
|
|
88
120
|
/// Prisma relation definition
|
|
89
121
|
user User @relation(fields: [userId], references: [id])
|
|
@@ -147,6 +179,7 @@ export type PostRelations = z.infer<typeof PostRelationsSchema>
|
|
|
147
179
|
```
|
|
148
180
|
|
|
149
181
|
## Valibot
|
|
182
|
+
|
|
150
183
|
```ts
|
|
151
184
|
import * as v from 'valibot'
|
|
152
185
|
|
|
@@ -184,29 +217,91 @@ export const PostSchema = v.object({
|
|
|
184
217
|
|
|
185
218
|
export type Post = v.InferInput<typeof PostSchema>
|
|
186
219
|
|
|
187
|
-
export const UserRelationsSchema = v.object({
|
|
220
|
+
export const UserRelationsSchema = v.object({
|
|
221
|
+
...UserSchema.entries,
|
|
222
|
+
posts: v.array(PostSchema),
|
|
223
|
+
})
|
|
188
224
|
|
|
189
225
|
export type UserRelations = v.InferInput<typeof UserRelationsSchema>
|
|
190
226
|
|
|
191
|
-
export const PostRelationsSchema = v.object({
|
|
227
|
+
export const PostRelationsSchema = v.object({
|
|
228
|
+
...PostSchema.entries,
|
|
229
|
+
user: UserSchema,
|
|
230
|
+
})
|
|
192
231
|
|
|
193
232
|
export type PostRelations = v.InferInput<typeof PostRelationsSchema>
|
|
194
233
|
```
|
|
195
234
|
|
|
235
|
+
## ArkType
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { type } from 'arktype'
|
|
239
|
+
|
|
240
|
+
export const UserSchema = type({
|
|
241
|
+
/** Primary key */
|
|
242
|
+
id: 'string.uuid',
|
|
243
|
+
/** Display name */
|
|
244
|
+
name: '1 <= string <= 50',
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
export type User = typeof UserSchema.infer
|
|
248
|
+
|
|
249
|
+
export const PostSchema = type({
|
|
250
|
+
/** Primary key */
|
|
251
|
+
id: 'string.uuid',
|
|
252
|
+
/** Article title */
|
|
253
|
+
title: '1 <= string <= 100',
|
|
254
|
+
/** Body content (no length limit) */
|
|
255
|
+
content: 'string',
|
|
256
|
+
/** Foreign key referencing User.id */
|
|
257
|
+
userId: 'string.uuid',
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
export type Post = typeof PostSchema.infer
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Effect Schema
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { Schema } from 'effect'
|
|
267
|
+
|
|
268
|
+
export const UserSchema = Schema.Struct({
|
|
269
|
+
/** Primary key */
|
|
270
|
+
id: Schema.UUID,
|
|
271
|
+
/** Display name */
|
|
272
|
+
name: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(50)),
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
export type User = Schema.Schema.Type<typeof UserSchema>
|
|
276
|
+
|
|
277
|
+
export const PostSchema = Schema.Struct({
|
|
278
|
+
/** Primary key */
|
|
279
|
+
id: Schema.UUID,
|
|
280
|
+
/** Article title */
|
|
281
|
+
title: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(100)),
|
|
282
|
+
/** Body content (no length limit) */
|
|
283
|
+
content: Schema.String,
|
|
284
|
+
/** Foreign key referencing User.id */
|
|
285
|
+
userId: Schema.UUID,
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
export type Post = Schema.Schema.Type<typeof PostSchema>
|
|
289
|
+
```
|
|
290
|
+
|
|
196
291
|
## Mermaid
|
|
197
292
|
|
|
198
293
|
```mermaid
|
|
199
294
|
erDiagram
|
|
200
295
|
User ||--}| Post : "(id) - (userId)"
|
|
201
296
|
User {
|
|
202
|
-
|
|
203
|
-
|
|
297
|
+
string id PK "Primary key"
|
|
298
|
+
string name "Display name"
|
|
204
299
|
}
|
|
205
300
|
Post {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
301
|
+
string id PK "Primary key"
|
|
302
|
+
string title "Article title"
|
|
303
|
+
string content "Body content (no length limit)"
|
|
304
|
+
string userId FK "Foreign key referencing User.id"
|
|
210
305
|
}
|
|
211
306
|
```
|
|
212
307
|
|
|
@@ -250,6 +345,32 @@ defmodule DBSchema.Post do
|
|
|
250
345
|
end
|
|
251
346
|
```
|
|
252
347
|
|
|
348
|
+
## DBML
|
|
349
|
+
|
|
350
|
+
```dbml
|
|
351
|
+
Table User {
|
|
352
|
+
id String [pk, note: 'Primary key']
|
|
353
|
+
name String [not null, note: 'Display name']
|
|
354
|
+
posts Post [not null, note: 'One-to-many relation to Post']
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
Table Post {
|
|
358
|
+
id String [pk, note: 'Primary key']
|
|
359
|
+
title String [not null, note: 'Article title']
|
|
360
|
+
content String [not null, note: 'Body content (no length limit)']
|
|
361
|
+
userId String [not null, note: 'Foreign key referencing User.id']
|
|
362
|
+
user User [not null, note: 'Prisma relation definition']
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
Ref Post_userId_fk: Post.userId > User.id
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## PNG/SVG
|
|
369
|
+
|
|
370
|
+
The `hekireki-svg` generator outputs ER diagrams as PNG or SVG images using [dbml-renderer](https://github.com/softwaretechnik-berlin/dbml-renderer).
|
|
371
|
+
|
|
372
|
+
Output: `docs/er-diagram.png`
|
|
373
|
+
|
|
253
374
|
## Configuration
|
|
254
375
|
|
|
255
376
|
### Zod Generator Options
|
|
@@ -273,6 +394,24 @@ end
|
|
|
273
394
|
| `comment` | `boolean` | `false` | Include schema documentation |
|
|
274
395
|
| `relation` | `boolean` | `false` | Generate relation schemas |
|
|
275
396
|
|
|
397
|
+
### ArkType Generator Options
|
|
398
|
+
|
|
399
|
+
| Option | Type | Default | Description |
|
|
400
|
+
|--------------|-----------|-------------------------------------|--------------------------------------------------|
|
|
401
|
+
| `output` | `string` | `./arktype` | Output directory |
|
|
402
|
+
| `file` | `string` | `index.ts` | File Name |
|
|
403
|
+
| `type` | `boolean` | `false` | Generate TypeScript types |
|
|
404
|
+
| `comment` | `boolean` | `false` | Include schema documentation |
|
|
405
|
+
|
|
406
|
+
### Effect Schema Generator Options
|
|
407
|
+
|
|
408
|
+
| Option | Type | Default | Description |
|
|
409
|
+
|--------------|-----------|-------------------------------------|--------------------------------------------------|
|
|
410
|
+
| `output` | `string` | `./effect` | Output directory |
|
|
411
|
+
| `file` | `string` | `index.ts` | File Name |
|
|
412
|
+
| `type` | `boolean` | `false` | Generate TypeScript types |
|
|
413
|
+
| `comment` | `boolean` | `false` | Include schema documentation |
|
|
414
|
+
|
|
276
415
|
### Mermaid ER Generator Options
|
|
277
416
|
|
|
278
417
|
| Option | Type | Default | Description |
|
|
@@ -285,13 +424,34 @@ end
|
|
|
285
424
|
| Option | Type | Default | Description |
|
|
286
425
|
|--------------|-----------|-------------------------------------|--------------------------------------------------|
|
|
287
426
|
| `output` | `string` | `./ecto` | Output directory |
|
|
288
|
-
| `app` | `string` | `MyApp` | App Name
|
|
427
|
+
| `app` | `string` | `MyApp` | App Name |
|
|
428
|
+
|
|
429
|
+
### DBML Generator Options
|
|
430
|
+
|
|
431
|
+
| Option | Type | Default | Description |
|
|
432
|
+
|--------------|-----------|-------------------------------------|--------------------------------------------------|
|
|
433
|
+
| `output` | `string` | `./dbml` | Output directory |
|
|
434
|
+
| `file` | `string` | `schema.dbml` | File Name |
|
|
435
|
+
|
|
436
|
+
### SVG Generator Options
|
|
437
|
+
|
|
438
|
+
| Option | Type | Default | Description |
|
|
439
|
+
|--------------|-----------|-------------------------------------|--------------------------------------------------|
|
|
440
|
+
| `output` | `string` | `./docs` | Output directory |
|
|
441
|
+
| `file` | `string` | `er-diagram` | File Name (without extension) |
|
|
442
|
+
| `format` | `string` | `png` | Output format (`png`, `svg`, or `dot`) |
|
|
289
443
|
|
|
290
|
-
|
|
444
|
+
## Annotation Prefixes
|
|
291
445
|
|
|
292
|
-
|
|
446
|
+
Each generator uses a specific annotation prefix in Prisma schema comments:
|
|
293
447
|
|
|
448
|
+
| Generator | Prefix | Example |
|
|
449
|
+
|----------------|--------|------------------------------------------------------------|
|
|
450
|
+
| Zod | `@z.` | `/// @z.uuid()` |
|
|
451
|
+
| Valibot | `@v.` | `/// @v.pipe(v.string(), v.uuid())` |
|
|
452
|
+
| ArkType | `@a.` | `/// @a."string.uuid"` |
|
|
453
|
+
| Effect Schema | `@e.` | `/// @e.Schema.UUID` |
|
|
294
454
|
|
|
295
455
|
## License
|
|
296
456
|
|
|
297
|
-
Distributed under the MIT License. See [LICENSE](https://github.com/nakita628/hekireki?tab=MIT-1-ov-file) for more information.
|
|
457
|
+
Distributed under the MIT License. See [LICENSE](https://github.com/nakita628/hekireki?tab=MIT-1-ov-file) for more information.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { escapeNote, formatConstraints, generateEnum, generateIndex, generateRef, quote } from "utils-lab";
|
|
2
|
+
|
|
3
|
+
//#region src/generator/dbml/generator/dbml-content.ts
|
|
4
|
+
/**
|
|
5
|
+
* Strip validation annotations (@z.*, @v.*, @a.*, @e.*) and relation annotations (@relation) from documentation
|
|
6
|
+
*/
|
|
7
|
+
function stripAnnotations(doc) {
|
|
8
|
+
if (!doc) return void 0;
|
|
9
|
+
const result = doc.split("\n").filter((line) => {
|
|
10
|
+
const trimmed = line.trim();
|
|
11
|
+
return !trimmed.startsWith("@z.") && !trimmed.startsWith("@v.") && !trimmed.startsWith("@a.") && !trimmed.startsWith("@e.") && !trimmed.startsWith("@relation");
|
|
12
|
+
}).join("\n").trim();
|
|
13
|
+
return result.length > 0 ? result : void 0;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Convert Prisma field to DBMLColumn
|
|
17
|
+
*/
|
|
18
|
+
function toDBMLColumn(field, models, mapToDbSchema) {
|
|
19
|
+
let fieldType = field.type;
|
|
20
|
+
if (mapToDbSchema) {
|
|
21
|
+
const relatedModel = models.find((m) => m.name === field.type);
|
|
22
|
+
if (relatedModel?.dbName) fieldType = relatedModel.dbName;
|
|
23
|
+
}
|
|
24
|
+
if (field.isList && !field.relationName) fieldType = `${fieldType}[]`;
|
|
25
|
+
let defaultValue;
|
|
26
|
+
const defaultDef = field.default;
|
|
27
|
+
if (defaultDef?.name === "autoincrement") {} else if (defaultDef?.name === "now") defaultValue = "`now()`";
|
|
28
|
+
else if (field.hasDefaultValue && typeof field.default !== "object") if (field.type === "String" || field.type === "Json" || field.kind === "enum") defaultValue = `'${field.default}'`;
|
|
29
|
+
else defaultValue = String(field.default);
|
|
30
|
+
return {
|
|
31
|
+
name: field.name,
|
|
32
|
+
type: fieldType,
|
|
33
|
+
isPrimaryKey: field.isId,
|
|
34
|
+
isIncrement: defaultDef?.name === "autoincrement",
|
|
35
|
+
isUnique: field.isUnique,
|
|
36
|
+
isNotNull: field.isRequired && !field.isId,
|
|
37
|
+
defaultValue,
|
|
38
|
+
note: stripAnnotations(field.documentation)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate custom column line with Prisma-specific formatting
|
|
43
|
+
*/
|
|
44
|
+
function generatePrismaColumn(column) {
|
|
45
|
+
const constraints = [];
|
|
46
|
+
if (column.isPrimaryKey) constraints.push("pk");
|
|
47
|
+
if (column.isIncrement) constraints.push("increment");
|
|
48
|
+
if (column.defaultValue !== void 0) constraints.push(`default: ${column.defaultValue}`);
|
|
49
|
+
if (column.isUnique) constraints.push("unique");
|
|
50
|
+
if (column.isNotNull) constraints.push("not null");
|
|
51
|
+
if (column.note) constraints.push(`note: ${quote(column.note)}`);
|
|
52
|
+
return ` ${column.name} ${column.type}${formatConstraints(constraints)}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Generate table indexes block
|
|
56
|
+
*/
|
|
57
|
+
function generateTableIndexes(model) {
|
|
58
|
+
const indexes = [];
|
|
59
|
+
const primaryFields = model.primaryKey?.fields;
|
|
60
|
+
if (primaryFields && primaryFields.length > 0) indexes.push({
|
|
61
|
+
columns: primaryFields,
|
|
62
|
+
isPrimaryKey: true
|
|
63
|
+
});
|
|
64
|
+
for (const composite of model.uniqueFields) if (composite.length > 1) indexes.push({
|
|
65
|
+
columns: composite,
|
|
66
|
+
isUnique: true
|
|
67
|
+
});
|
|
68
|
+
return indexes;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Generate table definitions
|
|
72
|
+
*/
|
|
73
|
+
function generateTables(models, mapToDbSchema = false, includeRelationFields = true) {
|
|
74
|
+
return models.map((model) => {
|
|
75
|
+
const modelName = mapToDbSchema && model.dbName ? model.dbName : model.name;
|
|
76
|
+
const columnLines = (includeRelationFields ? model.fields : model.fields.filter((field) => !field.relationName)).map((field) => toDBMLColumn(field, models, mapToDbSchema)).map(generatePrismaColumn).join("\n");
|
|
77
|
+
const indexes = generateTableIndexes(model);
|
|
78
|
+
const indexBlock = indexes.length > 0 ? `\n\n indexes {\n${indexes.map(generateIndex).join("\n")}\n }` : "";
|
|
79
|
+
const strippedNote = stripAnnotations(model.documentation);
|
|
80
|
+
return `Table ${modelName} {\n${columnLines}${indexBlock}${strippedNote ? `\n\n Note: ${quote(escapeNote(strippedNote))}` : ""}\n}`;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Generate enum definitions
|
|
85
|
+
*/
|
|
86
|
+
function generateEnums(enums) {
|
|
87
|
+
return enums.map((e) => {
|
|
88
|
+
return generateEnum({
|
|
89
|
+
name: e.name,
|
|
90
|
+
values: e.values.map((v) => v.name)
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get relation operator based on cardinality
|
|
96
|
+
*/
|
|
97
|
+
function getRelationOperator(models, from, to) {
|
|
98
|
+
return (models.find((m) => m.name === to)?.fields.find((f) => f.type === from))?.isList ? ">" : "-";
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Combine keys for composite foreign keys
|
|
102
|
+
*/
|
|
103
|
+
function combineKeys(keys) {
|
|
104
|
+
return keys.length > 1 ? `(${keys.join(", ")})` : keys[0];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Generate foreign key references
|
|
108
|
+
*/
|
|
109
|
+
function generateRelations(models, mapToDbSchema = false) {
|
|
110
|
+
const refs = [];
|
|
111
|
+
for (const model of models) {
|
|
112
|
+
const relFields = model.fields.filter((field) => field.relationName && field.relationToFields?.length && field.relationFromFields?.length);
|
|
113
|
+
for (const field of relFields) {
|
|
114
|
+
const relationFrom = model.name;
|
|
115
|
+
const relationTo = field.type;
|
|
116
|
+
const operator = getRelationOperator(models, relationFrom, relationTo);
|
|
117
|
+
const relationFromName = mapToDbSchema && model.dbName ? model.dbName : model.name;
|
|
118
|
+
const relatedModel = models.find((m) => m.name === relationTo);
|
|
119
|
+
const relationToName = mapToDbSchema && relatedModel?.dbName ? relatedModel.dbName : relationTo;
|
|
120
|
+
const fromColumn = combineKeys(field.relationFromFields ?? []);
|
|
121
|
+
const toColumn = combineKeys(field.relationToFields ?? []);
|
|
122
|
+
const ref = {
|
|
123
|
+
name: `${relationFromName}_${fromColumn}_fk`,
|
|
124
|
+
fromTable: relationFromName,
|
|
125
|
+
fromColumn,
|
|
126
|
+
toTable: relationToName,
|
|
127
|
+
toColumn,
|
|
128
|
+
type: operator,
|
|
129
|
+
onDelete: field.relationOnDelete
|
|
130
|
+
};
|
|
131
|
+
refs.push(generateRef(ref));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return refs;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Generate complete DBML content from Prisma DMMF
|
|
138
|
+
*/
|
|
139
|
+
function dbmlContent(datamodel, mapToDbSchema = false, includeRelationFields = true) {
|
|
140
|
+
const tables = generateTables(datamodel.models, mapToDbSchema, includeRelationFields);
|
|
141
|
+
const enums = generateEnums(datamodel.enums);
|
|
142
|
+
const refs = generateRelations(datamodel.models, mapToDbSchema);
|
|
143
|
+
return [
|
|
144
|
+
...enums,
|
|
145
|
+
...tables,
|
|
146
|
+
...refs
|
|
147
|
+
].join("\n\n");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//#endregion
|
|
151
|
+
export { dbmlContent as t };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fsp from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
//#region src/shared/fsp/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Creates a directory if it does not already exist.
|
|
6
|
+
*
|
|
7
|
+
* @param dir - Directory path to create.
|
|
8
|
+
* @returns A `Result` that is `ok` on success, otherwise an error message.
|
|
9
|
+
*/
|
|
10
|
+
async function mkdir(dir) {
|
|
11
|
+
try {
|
|
12
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
13
|
+
return {
|
|
14
|
+
ok: true,
|
|
15
|
+
value: void 0
|
|
16
|
+
};
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
error: e instanceof Error ? e.message : String(e)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Writes UTF-8 text to a file, creating it if necessary.
|
|
26
|
+
*
|
|
27
|
+
* @param path - File path to write.
|
|
28
|
+
* @param data - Text data to write.
|
|
29
|
+
* @returns A `Result` that is `ok` on success, otherwise an error message.
|
|
30
|
+
*/
|
|
31
|
+
async function writeFile(path, data) {
|
|
32
|
+
try {
|
|
33
|
+
await fsp.writeFile(path, data, "utf-8");
|
|
34
|
+
return {
|
|
35
|
+
ok: true,
|
|
36
|
+
value: void 0
|
|
37
|
+
};
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
error: e instanceof Error ? e.message : String(e)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Writes binary data to a file, creating it if necessary.
|
|
47
|
+
*
|
|
48
|
+
* @param path - File path to write.
|
|
49
|
+
* @param data - Binary data to write.
|
|
50
|
+
* @returns A `Result` that is `ok` on success, otherwise an error message.
|
|
51
|
+
*/
|
|
52
|
+
async function writeFileBinary(path, data) {
|
|
53
|
+
try {
|
|
54
|
+
await fsp.writeFile(path, data);
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
value: void 0
|
|
58
|
+
};
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
error: e instanceof Error ? e.message : String(e)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
export { writeFile as n, writeFileBinary as r, mkdir as t };
|
|
@@ -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/arktype/generator/schema.ts
|
|
9
|
+
/**
|
|
10
|
+
* Generate ArkType schema
|
|
11
|
+
* @param modelName - The name of the model
|
|
12
|
+
* @param fields - The fields of the model
|
|
13
|
+
* @returns The generated ArkType schema
|
|
14
|
+
*/
|
|
15
|
+
function schema(modelName, fields) {
|
|
16
|
+
return `export const ${modelName}Schema = type({\n${fields}\n})`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/generator/arktype/generator/schemas.ts
|
|
21
|
+
/**
|
|
22
|
+
* Generate properties for ArkType schema.
|
|
23
|
+
*/
|
|
24
|
+
function arktypePropertiesGenerator(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 ?? "\"unknown\""},`;
|
|
27
|
+
}).join("\n");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates ArkType 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 ArkType schemas
|
|
35
|
+
*/
|
|
36
|
+
function schemas(modelFields, comment) {
|
|
37
|
+
return schemaFromFields(modelFields, comment, schema, arktypePropertiesGenerator);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/generator/arktype/generator/arktype.ts
|
|
42
|
+
/**
|
|
43
|
+
* Generate ArkType infer type statement.
|
|
44
|
+
* @param modelName - The name of the model
|
|
45
|
+
* @returns The generated type inference statement
|
|
46
|
+
*/
|
|
47
|
+
function makeArktypeInfer(modelName) {
|
|
48
|
+
return `export type ${modelName} = typeof ${modelName}Schema.infer`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates ArkType schemas and types from models.
|
|
52
|
+
*
|
|
53
|
+
* @param models - The models to generate the ArkType 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 ArkType schemas and types
|
|
57
|
+
*/
|
|
58
|
+
function arktype(models, type, comment) {
|
|
59
|
+
return validationSchemas(models, type, comment, {
|
|
60
|
+
importStatement: `import { type } from 'arktype'`,
|
|
61
|
+
annotationPrefix: "@a.",
|
|
62
|
+
parseDocument: parseDocumentWithoutAnnotations,
|
|
63
|
+
extractValidation: makeValidationExtractor("@a."),
|
|
64
|
+
inferType: makeArktypeInfer,
|
|
65
|
+
schemas
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/generator/arktype/index.ts
|
|
71
|
+
const { generatorHandler } = pkg;
|
|
72
|
+
const emit = async (options) => {
|
|
73
|
+
const outDir = options.generator.output?.value ?? "./arktype";
|
|
74
|
+
const file = getString(options.generator.config?.file, "index.ts") ?? "index.ts";
|
|
75
|
+
const fmtResult = await fmt(arktype(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: "./arktype/",
|
|
87
|
+
prettyName: "Hekireki-ArkType"
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
onGenerate
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { onGenerate };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as writeFile, 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
|
+
|
|
6
|
+
//#region src/generator/dbml/index.ts
|
|
7
|
+
const { generatorHandler } = pkg;
|
|
8
|
+
/**
|
|
9
|
+
* Get string value from config
|
|
10
|
+
*/
|
|
11
|
+
function getStringValue(value) {
|
|
12
|
+
if (value === void 0) return void 0;
|
|
13
|
+
return Array.isArray(value) ? value[0] : value;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get boolean option from config
|
|
17
|
+
*/
|
|
18
|
+
function getBoolOption(config, key, defaultValue) {
|
|
19
|
+
const value = getStringValue(config[key]);
|
|
20
|
+
if (value === void 0) return defaultValue;
|
|
21
|
+
return value.toLowerCase() !== "false";
|
|
22
|
+
}
|
|
23
|
+
async function main(options) {
|
|
24
|
+
const { config } = options.generator;
|
|
25
|
+
const mapToDbSchema = getBoolOption(config, "mapToDbSchema", true);
|
|
26
|
+
const includeRelationFields = getBoolOption(config, "includeRelationFields", true);
|
|
27
|
+
const content = dbmlContent(options.dmmf.datamodel, mapToDbSchema, includeRelationFields);
|
|
28
|
+
const output = options.generator.output?.value ?? "./dbml";
|
|
29
|
+
const file = getStringValue(config.file) ?? "schema.dbml";
|
|
30
|
+
const isOutputFile = output.includes(".");
|
|
31
|
+
const outputDir = isOutputFile ? "." : output;
|
|
32
|
+
const outputFile = isOutputFile ? output : `${output}/${file}`;
|
|
33
|
+
const mkdirResult = await mkdir(outputDir);
|
|
34
|
+
if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
|
|
35
|
+
const writeResult = await writeFile(outputFile, content);
|
|
36
|
+
if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
|
|
37
|
+
}
|
|
38
|
+
generatorHandler({
|
|
39
|
+
onManifest() {
|
|
40
|
+
return {
|
|
41
|
+
defaultOutput: "./dbml",
|
|
42
|
+
prettyName: "Hekireki-DBML"
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
onGenerate: main
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { main };
|