hekireki 0.4.1 โ 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 -24
- 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,16 +1,16 @@
|
|
|
1
1
|
# Hekireki
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<img src="https://raw.githubusercontent.com/nakita628/hekireki/refs/heads/main/assets/img/hekireki.png"/>
|
|
5
|
-
</p>
|
|
6
|
-
|
|
7
|
-
**[Hekireki](https://www.npmjs.com/package/hekireki)** is a tool that generates validation schemas for Zod and Valibot, as well as ER diagrams, from [Prisma](https://www.prisma.io/) schemas annotated with comments.
|
|
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.
|
|
8
4
|
|
|
9
5
|
## Features
|
|
10
6
|
|
|
11
7
|
- ๐ Automatically generates [Zod](https://zod.dev/) schemas from your Prisma schema
|
|
12
8
|
- ๐ค Automatically generates [Valibot](https://valibot.dev/) schemas from your Prisma schema
|
|
13
|
-
-
|
|
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)
|
|
14
14
|
- ๐งช Generates [Ecto](https://hexdocs.pm/ecto/Ecto.Schema.html) schemas for Elixir projects
|
|
15
15
|
โ ๏ธ Foreign key constraints are **not** included โ manage relationships in your application logic
|
|
16
16
|
|
|
@@ -25,13 +25,8 @@ npm install -D hekireki
|
|
|
25
25
|
Prepare `schema.prisma`:
|
|
26
26
|
|
|
27
27
|
```prisma
|
|
28
|
-
generator client {
|
|
29
|
-
provider = "prisma-client-js"
|
|
30
|
-
}
|
|
31
|
-
|
|
32
28
|
datasource db {
|
|
33
29
|
provider = "sqlite"
|
|
34
|
-
url = env("DATABASE_URL")
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
generator Hekireki-ER {
|
|
@@ -52,42 +47,75 @@ generator Hekireki-Valibot {
|
|
|
52
47
|
relation = true
|
|
53
48
|
}
|
|
54
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
|
+
|
|
55
62
|
generator Hekireki-Ecto {
|
|
56
63
|
provider = "hekireki-ecto"
|
|
57
64
|
output = "schema"
|
|
58
65
|
app = "DBSchema"
|
|
59
66
|
}
|
|
60
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
|
+
|
|
61
78
|
model User {
|
|
62
79
|
/// Primary key
|
|
63
80
|
/// @z.uuid()
|
|
64
81
|
/// @v.pipe(v.string(), v.uuid())
|
|
82
|
+
/// @a."string.uuid"
|
|
83
|
+
/// @e.Schema.UUID
|
|
65
84
|
id String @id @default(uuid())
|
|
66
85
|
/// Display name
|
|
67
86
|
/// @z.string().min(1).max(50)
|
|
68
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))
|
|
69
90
|
name String
|
|
70
91
|
/// One-to-many relation to Post
|
|
71
92
|
posts Post[]
|
|
72
93
|
}
|
|
73
94
|
|
|
74
|
-
/// @relation User.id Post.userId one-to-many
|
|
75
95
|
model Post {
|
|
76
96
|
/// Primary key
|
|
77
97
|
/// @z.uuid()
|
|
78
98
|
/// @v.pipe(v.string(), v.uuid())
|
|
99
|
+
/// @a."string.uuid"
|
|
100
|
+
/// @e.Schema.UUID
|
|
79
101
|
id String @id @default(uuid())
|
|
80
102
|
/// Article title
|
|
81
103
|
/// @z.string().min(1).max(100)
|
|
82
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))
|
|
83
107
|
title String
|
|
84
108
|
/// Body content (no length limit)
|
|
85
109
|
/// @z.string()
|
|
86
110
|
/// @v.string()
|
|
111
|
+
/// @a."string"
|
|
112
|
+
/// @e.Schema.String
|
|
87
113
|
content String
|
|
88
114
|
/// Foreign key referencing User.id
|
|
89
115
|
/// @z.uuid()
|
|
90
116
|
/// @v.pipe(v.string(), v.uuid())
|
|
117
|
+
/// @a."string.uuid"
|
|
118
|
+
/// @e.Schema.UUID
|
|
91
119
|
userId String
|
|
92
120
|
/// Prisma relation definition
|
|
93
121
|
user User @relation(fields: [userId], references: [id])
|
|
@@ -151,6 +179,7 @@ export type PostRelations = z.infer<typeof PostRelationsSchema>
|
|
|
151
179
|
```
|
|
152
180
|
|
|
153
181
|
## Valibot
|
|
182
|
+
|
|
154
183
|
```ts
|
|
155
184
|
import * as v from 'valibot'
|
|
156
185
|
|
|
@@ -188,29 +217,91 @@ export const PostSchema = v.object({
|
|
|
188
217
|
|
|
189
218
|
export type Post = v.InferInput<typeof PostSchema>
|
|
190
219
|
|
|
191
|
-
export const UserRelationsSchema = v.object({
|
|
220
|
+
export const UserRelationsSchema = v.object({
|
|
221
|
+
...UserSchema.entries,
|
|
222
|
+
posts: v.array(PostSchema),
|
|
223
|
+
})
|
|
192
224
|
|
|
193
225
|
export type UserRelations = v.InferInput<typeof UserRelationsSchema>
|
|
194
226
|
|
|
195
|
-
export const PostRelationsSchema = v.object({
|
|
227
|
+
export const PostRelationsSchema = v.object({
|
|
228
|
+
...PostSchema.entries,
|
|
229
|
+
user: UserSchema,
|
|
230
|
+
})
|
|
196
231
|
|
|
197
232
|
export type PostRelations = v.InferInput<typeof PostRelationsSchema>
|
|
198
233
|
```
|
|
199
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
|
+
|
|
200
291
|
## Mermaid
|
|
201
292
|
|
|
202
293
|
```mermaid
|
|
203
294
|
erDiagram
|
|
204
295
|
User ||--}| Post : "(id) - (userId)"
|
|
205
296
|
User {
|
|
206
|
-
|
|
207
|
-
|
|
297
|
+
string id PK "Primary key"
|
|
298
|
+
string name "Display name"
|
|
208
299
|
}
|
|
209
300
|
Post {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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"
|
|
214
305
|
}
|
|
215
306
|
```
|
|
216
307
|
|
|
@@ -254,6 +345,32 @@ defmodule DBSchema.Post do
|
|
|
254
345
|
end
|
|
255
346
|
```
|
|
256
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
|
+
|
|
257
374
|
## Configuration
|
|
258
375
|
|
|
259
376
|
### Zod Generator Options
|
|
@@ -277,6 +394,24 @@ end
|
|
|
277
394
|
| `comment` | `boolean` | `false` | Include schema documentation |
|
|
278
395
|
| `relation` | `boolean` | `false` | Generate relation schemas |
|
|
279
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
|
+
|
|
280
415
|
### Mermaid ER Generator Options
|
|
281
416
|
|
|
282
417
|
| Option | Type | Default | Description |
|
|
@@ -289,13 +424,34 @@ end
|
|
|
289
424
|
| Option | Type | Default | Description |
|
|
290
425
|
|--------------|-----------|-------------------------------------|--------------------------------------------------|
|
|
291
426
|
| `output` | `string` | `./ecto` | Output directory |
|
|
292
|
-
| `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`) |
|
|
293
443
|
|
|
294
|
-
|
|
444
|
+
## Annotation Prefixes
|
|
295
445
|
|
|
296
|
-
|
|
446
|
+
Each generator uses a specific annotation prefix in Prisma schema comments:
|
|
297
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` |
|
|
298
454
|
|
|
299
455
|
## License
|
|
300
456
|
|
|
301
|
-
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 };
|