hekireki 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -14
- package/dist/generator/ajv/index.d.ts +6 -0
- package/dist/generator/ajv/index.js +87 -0
- package/dist/generator/arktype/index.js +17 -3
- package/dist/generator/dbml/index.js +43 -33
- package/dist/generator/drizzle/index.js +15 -24
- package/dist/generator/ecto/index.js +34 -9
- package/dist/generator/effect/index.js +17 -3
- package/dist/generator/gorm/index.d.ts +6 -0
- package/dist/generator/gorm/index.js +370 -0
- package/dist/generator/mermaid-er/index.js +6 -12
- package/dist/generator/sea-orm/index.d.ts +6 -0
- package/dist/generator/sea-orm/index.js +444 -0
- package/dist/generator/sqlalchemy/index.d.ts +6 -0
- package/dist/generator/sqlalchemy/index.js +458 -0
- package/dist/generator/typebox/index.d.ts +6 -0
- package/dist/generator/typebox/index.js +93 -0
- package/dist/generator/valibot/index.js +15 -5
- package/dist/generator/zod/index.js +15 -5
- package/dist/{prisma-Cc0YxSiO.js → prisma-ChsFqlYX.js} +6 -6
- package/dist/utils-COHZyQue.js +116 -0
- package/package.json +18 -7
- package/dist/utils-DeZn2r_T.js +0 -287
package/README.md
CHANGED
|
@@ -2,18 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Hekireki
|
|
4
4
|
|
|
5
|
-
**[Hekireki](https://www.npmjs.com/package/hekireki)** is a tool that generates validation schemas
|
|
5
|
+
**[Hekireki](https://www.npmjs.com/package/hekireki)** is a tool that generates validation schemas, ORM models, and ER diagrams from [Prisma](https://www.prisma.io/) schemas — supporting TypeScript, Python, Go, Rust, and Elixir.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
+
### TypeScript Validation Libraries
|
|
10
|
+
|
|
9
11
|
- 💎 Automatically generates [Zod](https://zod.dev/) schemas from your Prisma schema
|
|
10
12
|
- 🤖 Automatically generates [Valibot](https://valibot.dev/) schemas from your Prisma schema
|
|
11
13
|
- 🏹 Automatically generates [ArkType](https://arktype.io/) schemas from your Prisma schema
|
|
12
14
|
- ⚡ Automatically generates [Effect Schema](https://effect.website/docs/schema/introduction/) from your Prisma schema
|
|
15
|
+
- 📦 Automatically generates [TypeBox](https://github.com/sinclairzx81/typebox) schemas from your Prisma schema
|
|
16
|
+
- 📋 Automatically generates [AJV](https://ajv.js.org/)-compatible JSON Schema objects from your Prisma schema
|
|
17
|
+
|
|
18
|
+
### ORM / Schema Generation (Multi-Language)
|
|
19
|
+
|
|
13
20
|
- 🗄️ Automatically generates [Drizzle ORM](https://orm.drizzle.team/) table schemas and relations from your Prisma schema
|
|
21
|
+
- 🐍 Automatically generates [SQLAlchemy](https://www.sqlalchemy.org/) models (Python) — with `Mapped[T]` type hints, relationships, enums, composite keys, and index support
|
|
22
|
+
- 🐹 Automatically generates [GORM](https://gorm.io/) models (Go) — with struct tags, JSON tags, relationships, enums, composite keys, and index support
|
|
23
|
+
- 🦀 Automatically generates [Sea-ORM](https://www.sea-ql.org/SeaORM/) entities (Rust) — with `DeriveEntityModel`, relations, enums, serde support, and `rename_all`
|
|
24
|
+
- 🧪 Generates [Ecto](https://hexdocs.pm/ecto/Ecto.Schema.html) schemas (Elixir) — with associations (`belongs_to`, `has_many`, `has_one`), composite primary keys, `@type t` typespecs, array fields, `@@map`/`@map` support, and `@moduledoc`
|
|
25
|
+
|
|
26
|
+
### Diagrams & Documentation
|
|
27
|
+
|
|
14
28
|
- 📊 Creates [Mermaid](https://mermaid.js.org/) ER diagrams with PK/FK markers
|
|
15
29
|
- 📝 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`)
|
|
16
|
-
- 🧪 Generates [Ecto](https://hexdocs.pm/ecto/Ecto.Schema.html) schemas for Elixir projects — with associations (`belongs_to`, `has_many`, `has_one`), composite primary keys, `@type t` typespecs, array fields, `@@map`/`@map` support, and `@moduledoc`
|
|
17
30
|
|
|
18
31
|
## Installation
|
|
19
32
|
|
|
@@ -62,10 +75,41 @@ generator Hekireki-Effect {
|
|
|
62
75
|
relation = true
|
|
63
76
|
}
|
|
64
77
|
|
|
78
|
+
generator Hekireki-TypeBox {
|
|
79
|
+
provider = "hekireki-typebox"
|
|
80
|
+
type = true
|
|
81
|
+
comment = true
|
|
82
|
+
relation = true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
generator Hekireki-AJV {
|
|
86
|
+
provider = "hekireki-ajv"
|
|
87
|
+
type = true
|
|
88
|
+
comment = true
|
|
89
|
+
relation = true
|
|
90
|
+
}
|
|
91
|
+
|
|
65
92
|
generator Hekireki-Drizzle {
|
|
66
93
|
provider = "hekireki-drizzle"
|
|
67
94
|
}
|
|
68
95
|
|
|
96
|
+
generator Hekireki-SQLAlchemy {
|
|
97
|
+
provider = "hekireki-sqlalchemy"
|
|
98
|
+
output = "./sqlalchemy"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
generator Hekireki-GORM {
|
|
102
|
+
provider = "hekireki-gorm"
|
|
103
|
+
output = "./gorm"
|
|
104
|
+
package = "model"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
generator Hekireki-SeaORM {
|
|
108
|
+
provider = "hekireki-sea-orm"
|
|
109
|
+
output = "./sea_orm"
|
|
110
|
+
renameAll = "camelCase"
|
|
111
|
+
}
|
|
112
|
+
|
|
69
113
|
generator Hekireki-Ecto {
|
|
70
114
|
provider = "hekireki-ecto"
|
|
71
115
|
output = "./ecto"
|
|
@@ -88,12 +132,16 @@ model User {
|
|
|
88
132
|
/// @v.pipe(v.string(), v.uuid())
|
|
89
133
|
/// @a."string.uuid"
|
|
90
134
|
/// @e.Schema.UUID
|
|
135
|
+
/// @t.Type.String({ format: 'uuid' })
|
|
136
|
+
/// @j.{ type: 'string' as const, format: 'uuid' as const }
|
|
91
137
|
id String @id @default(uuid())
|
|
92
138
|
/// Display name
|
|
93
139
|
/// @z.string().min(1).max(50)
|
|
94
140
|
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(50))
|
|
95
141
|
/// @a."1 <= string <= 50"
|
|
96
142
|
/// @e.Schema.String.pipe(Schema.minLength(1), Schema.maxLength(50))
|
|
143
|
+
/// @t.Type.String({ minLength: 1, maxLength: 50 })
|
|
144
|
+
/// @j.{ type: 'string' as const, minLength: 1, maxLength: 50 }
|
|
97
145
|
name String
|
|
98
146
|
/// One-to-many relation to Post
|
|
99
147
|
posts Post[]
|
|
@@ -105,24 +153,32 @@ model Post {
|
|
|
105
153
|
/// @v.pipe(v.string(), v.uuid())
|
|
106
154
|
/// @a."string.uuid"
|
|
107
155
|
/// @e.Schema.UUID
|
|
156
|
+
/// @t.Type.String({ format: 'uuid' })
|
|
157
|
+
/// @j.{ type: 'string' as const, format: 'uuid' as const }
|
|
108
158
|
id String @id @default(uuid())
|
|
109
159
|
/// Article title
|
|
110
160
|
/// @z.string().min(1).max(100)
|
|
111
161
|
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(100))
|
|
112
162
|
/// @a."1 <= string <= 100"
|
|
113
163
|
/// @e.Schema.String.pipe(Schema.minLength(1), Schema.maxLength(100))
|
|
164
|
+
/// @t.Type.String({ minLength: 1, maxLength: 100 })
|
|
165
|
+
/// @j.{ type: 'string' as const, minLength: 1, maxLength: 100 }
|
|
114
166
|
title String
|
|
115
167
|
/// Body content (no length limit)
|
|
116
168
|
/// @z.string()
|
|
117
169
|
/// @v.string()
|
|
118
170
|
/// @a."string"
|
|
119
171
|
/// @e.Schema.String
|
|
172
|
+
/// @t.Type.String()
|
|
173
|
+
/// @j.{ type: 'string' as const }
|
|
120
174
|
content String
|
|
121
175
|
/// Foreign key referencing User.id
|
|
122
176
|
/// @z.uuid()
|
|
123
177
|
/// @v.pipe(v.string(), v.uuid())
|
|
124
178
|
/// @a."string.uuid"
|
|
125
179
|
/// @e.Schema.UUID
|
|
180
|
+
/// @t.Type.String({ format: 'uuid' })
|
|
181
|
+
/// @j.{ type: 'string' as const, format: 'uuid' as const }
|
|
126
182
|
userId String
|
|
127
183
|
/// Prisma relation definition
|
|
128
184
|
user User @relation(fields: [userId], references: [id])
|
|
@@ -295,6 +351,108 @@ export const PostSchema = Schema.Struct({
|
|
|
295
351
|
export type Post = Schema.Schema.Type<typeof PostSchema>
|
|
296
352
|
```
|
|
297
353
|
|
|
354
|
+
### TypeBox
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
import { type Static, Type } from '@sinclair/typebox'
|
|
358
|
+
|
|
359
|
+
export const UserSchema = Type.Object({
|
|
360
|
+
/** Primary key */
|
|
361
|
+
id: Type.String({ format: 'uuid' }),
|
|
362
|
+
/** Display name */
|
|
363
|
+
name: Type.String({ minLength: 1, maxLength: 50 }),
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
export type User = Static<typeof UserSchema>
|
|
367
|
+
|
|
368
|
+
export const PostSchema = Type.Object({
|
|
369
|
+
/** Primary key */
|
|
370
|
+
id: Type.String({ format: 'uuid' }),
|
|
371
|
+
/** Article title */
|
|
372
|
+
title: Type.String({ minLength: 1, maxLength: 100 }),
|
|
373
|
+
/** Body content (no length limit) */
|
|
374
|
+
content: Type.String(),
|
|
375
|
+
/** Foreign key referencing User.id */
|
|
376
|
+
userId: Type.String({ format: 'uuid' }),
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
export type Post = Static<typeof PostSchema>
|
|
380
|
+
|
|
381
|
+
export const UserRelationsSchema = Type.Object({
|
|
382
|
+
...UserSchema.properties,
|
|
383
|
+
posts: Type.Array(PostSchema),
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
export type UserRelations = Static<typeof UserRelationsSchema>
|
|
387
|
+
|
|
388
|
+
export const PostRelationsSchema = Type.Object({
|
|
389
|
+
...PostSchema.properties,
|
|
390
|
+
user: UserSchema,
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
export type PostRelations = Static<typeof PostRelationsSchema>
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### AJV (JSON Schema)
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
import type { FromSchema } from 'json-schema-to-ts'
|
|
400
|
+
|
|
401
|
+
export const UserSchema = {
|
|
402
|
+
type: 'object' as const,
|
|
403
|
+
properties: {
|
|
404
|
+
/** Primary key */
|
|
405
|
+
id: { type: 'string' as const, format: 'uuid' as const },
|
|
406
|
+
/** Display name */
|
|
407
|
+
name: { type: 'string' as const, minLength: 1, maxLength: 50 },
|
|
408
|
+
},
|
|
409
|
+
required: ['id', 'name'] as const,
|
|
410
|
+
additionalProperties: false,
|
|
411
|
+
} as const
|
|
412
|
+
|
|
413
|
+
export type User = FromSchema<typeof UserSchema>
|
|
414
|
+
|
|
415
|
+
export const PostSchema = {
|
|
416
|
+
type: 'object' as const,
|
|
417
|
+
properties: {
|
|
418
|
+
/** Primary key */
|
|
419
|
+
id: { type: 'string' as const, format: 'uuid' as const },
|
|
420
|
+
/** Article title */
|
|
421
|
+
title: { type: 'string' as const, minLength: 1, maxLength: 100 },
|
|
422
|
+
/** Body content (no length limit) */
|
|
423
|
+
content: { type: 'string' as const },
|
|
424
|
+
/** Foreign key referencing User.id */
|
|
425
|
+
userId: { type: 'string' as const, format: 'uuid' as const },
|
|
426
|
+
},
|
|
427
|
+
required: ['id', 'title', 'content', 'userId'] as const,
|
|
428
|
+
additionalProperties: false,
|
|
429
|
+
} as const
|
|
430
|
+
|
|
431
|
+
export type Post = FromSchema<typeof PostSchema>
|
|
432
|
+
|
|
433
|
+
export const UserRelationsSchema = {
|
|
434
|
+
type: 'object' as const,
|
|
435
|
+
properties: {
|
|
436
|
+
...UserSchema.properties,
|
|
437
|
+
posts: { type: 'array' as const, items: PostSchema },
|
|
438
|
+
},
|
|
439
|
+
additionalProperties: false,
|
|
440
|
+
} as const
|
|
441
|
+
|
|
442
|
+
export type UserRelations = FromSchema<typeof UserRelationsSchema>
|
|
443
|
+
|
|
444
|
+
export const PostRelationsSchema = {
|
|
445
|
+
type: 'object' as const,
|
|
446
|
+
properties: {
|
|
447
|
+
...PostSchema.properties,
|
|
448
|
+
user: UserSchema,
|
|
449
|
+
},
|
|
450
|
+
additionalProperties: false,
|
|
451
|
+
} as const
|
|
452
|
+
|
|
453
|
+
export type PostRelations = FromSchema<typeof PostRelationsSchema>
|
|
454
|
+
```
|
|
455
|
+
|
|
298
456
|
### Drizzle
|
|
299
457
|
|
|
300
458
|
```ts
|
|
@@ -390,18 +548,90 @@ defmodule DBSchema.Post do
|
|
|
390
548
|
end
|
|
391
549
|
```
|
|
392
550
|
|
|
393
|
-
|
|
551
|
+
### SQLAlchemy
|
|
394
552
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
553
|
+
```python
|
|
554
|
+
from sqlalchemy import ForeignKey
|
|
555
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
class Base(DeclarativeBase):
|
|
559
|
+
pass
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class User(Base):
|
|
563
|
+
__tablename__ = "user"
|
|
564
|
+
|
|
565
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
566
|
+
name: Mapped[str]
|
|
567
|
+
|
|
568
|
+
posts: Mapped[list["Post"]] = relationship(back_populates="user")
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
class Post(Base):
|
|
572
|
+
__tablename__ = "post"
|
|
573
|
+
|
|
574
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
575
|
+
title: Mapped[str]
|
|
576
|
+
content: Mapped[str]
|
|
577
|
+
user_id: Mapped[str] = mapped_column(ForeignKey("user.id"))
|
|
578
|
+
|
|
579
|
+
user: Mapped["User"] = relationship(back_populates="posts")
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### GORM
|
|
583
|
+
|
|
584
|
+
```go
|
|
585
|
+
package model
|
|
586
|
+
|
|
587
|
+
type User struct {
|
|
588
|
+
ID string `gorm:"column:id;primaryKey;type:char(36)" json:"id"`
|
|
589
|
+
Name string `gorm:"column:name;not null" json:"name"`
|
|
590
|
+
Posts []Post `gorm:"foreignKey:UserID"`
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
type Post struct {
|
|
594
|
+
ID string `gorm:"column:id;primaryKey;type:char(36)" json:"id"`
|
|
595
|
+
Title string `gorm:"column:title;not null" json:"title"`
|
|
596
|
+
Content string `gorm:"column:content;not null" json:"content"`
|
|
597
|
+
UserID string `gorm:"column:user_id;not null" json:"user_id"`
|
|
598
|
+
User User
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### Sea-ORM
|
|
603
|
+
|
|
604
|
+
Each model is output as a separate `.rs` file with `mod.rs` and `prelude.rs`, following Sea-ORM conventions.
|
|
605
|
+
|
|
606
|
+
**user.rs:**
|
|
607
|
+
|
|
608
|
+
```rust
|
|
609
|
+
use sea_orm::entity::prelude::*;
|
|
610
|
+
use serde::{Deserialize, Serialize};
|
|
611
|
+
|
|
612
|
+
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
|
613
|
+
#[serde(rename_all = "camelCase")]
|
|
614
|
+
#[sea_orm(table_name = "user")]
|
|
615
|
+
pub struct Model {
|
|
616
|
+
#[sea_orm(primary_key, auto_increment = false)]
|
|
617
|
+
pub id: String,
|
|
618
|
+
pub name: String,
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
622
|
+
pub enum Relation {
|
|
623
|
+
#[sea_orm(has_many = "super::post::Entity")]
|
|
624
|
+
Posts,
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
impl Related<super::post::Entity> for Entity {
|
|
628
|
+
fn to() -> RelationDef {
|
|
629
|
+
Relation::Posts.def()
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
impl ActiveModelBehavior for ActiveModel {}
|
|
634
|
+
```
|
|
405
635
|
|
|
406
636
|
### DBML
|
|
407
637
|
|
|
@@ -487,6 +717,24 @@ generator Hekireki-Effect {
|
|
|
487
717
|
relation = true // Generate relation schemas (default: false)
|
|
488
718
|
}
|
|
489
719
|
|
|
720
|
+
// TypeBox Generator
|
|
721
|
+
generator Hekireki-TypeBox {
|
|
722
|
+
provider = "hekireki-typebox"
|
|
723
|
+
output = "./typebox" // Output path (default: ./typebox/index.ts)
|
|
724
|
+
type = true // Generate TypeScript types (default: false)
|
|
725
|
+
comment = true // Include schema documentation (default: false)
|
|
726
|
+
relation = true // Generate relation schemas (default: false)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// AJV (JSON Schema) Generator
|
|
730
|
+
generator Hekireki-AJV {
|
|
731
|
+
provider = "hekireki-ajv"
|
|
732
|
+
output = "./ajv" // Output path (default: ./ajv/index.ts)
|
|
733
|
+
type = true // Generate TypeScript types (default: false)
|
|
734
|
+
comment = true // Include schema documentation (default: false)
|
|
735
|
+
relation = true // Generate relation schemas (default: false)
|
|
736
|
+
}
|
|
737
|
+
|
|
490
738
|
// Drizzle ORM Schema Generator
|
|
491
739
|
generator Hekireki-Drizzle {
|
|
492
740
|
provider = "hekireki-drizzle"
|
|
@@ -499,7 +747,27 @@ generator Hekireki-ER {
|
|
|
499
747
|
output = "./mermaid-er" // Output path (default: ./mermaid-er/ER.md)
|
|
500
748
|
}
|
|
501
749
|
|
|
502
|
-
//
|
|
750
|
+
// SQLAlchemy Generator (Python)
|
|
751
|
+
generator Hekireki-SQLAlchemy {
|
|
752
|
+
provider = "hekireki-sqlalchemy"
|
|
753
|
+
output = "./sqlalchemy" // Output path (default: ./sqlalchemy/models.py)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// GORM Generator (Go)
|
|
757
|
+
generator Hekireki-GORM {
|
|
758
|
+
provider = "hekireki-gorm"
|
|
759
|
+
output = "./gorm" // Output path (default: ./gorm/models.go)
|
|
760
|
+
package = "model" // Go package name (default: model)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Sea-ORM Generator (Rust)
|
|
764
|
+
generator Hekireki-SeaORM {
|
|
765
|
+
provider = "hekireki-sea-orm"
|
|
766
|
+
output = "./sea_orm" // Output directory for .rs files
|
|
767
|
+
renameAll = "camelCase" // #[serde(rename_all = "...")] attribute (optional)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Ecto Generator (Elixir)
|
|
503
771
|
generator Hekireki-Ecto {
|
|
504
772
|
provider = "hekireki-ecto"
|
|
505
773
|
output = "./ecto" // Output directory (default: ./ecto/)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as fmt } from "../../format-CzXgkLDe.js";
|
|
3
|
+
import { c as parseDocumentWithoutAnnotations, d as mkdir, f as writeFile, s as makeValidationExtractor, t as getBool } from "../../utils-COHZyQue.js";
|
|
4
|
+
import { n as validationSchemas, t as makeRelationsOnly } from "../../prisma-ChsFqlYX.js";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import pkg from "@prisma/generator-helper";
|
|
7
|
+
|
|
8
|
+
//#region src/helper/ajv.ts
|
|
9
|
+
function makeAjvInfer(modelName) {
|
|
10
|
+
return `export type ${modelName} = FromSchema<typeof ${modelName}Schema>`;
|
|
11
|
+
}
|
|
12
|
+
function makeAjvEnumExpression(values) {
|
|
13
|
+
return `{ enum: [${values.map((v) => `'${v}'`).join(", ")}] as const }`;
|
|
14
|
+
}
|
|
15
|
+
const PRISMA_TO_AJV = {
|
|
16
|
+
String: "{ type: 'string' as const }",
|
|
17
|
+
Int: "{ type: 'integer' as const }",
|
|
18
|
+
Float: "{ type: 'number' as const }",
|
|
19
|
+
Boolean: "{ type: 'boolean' as const }",
|
|
20
|
+
DateTime: "{ type: 'string' as const, format: 'date-time' as const }",
|
|
21
|
+
BigInt: "{ type: 'integer' as const }",
|
|
22
|
+
Decimal: "{ type: 'number' as const }",
|
|
23
|
+
Json: "{}",
|
|
24
|
+
Bytes: "{ type: 'string' as const }"
|
|
25
|
+
};
|
|
26
|
+
function makeAjvSchemas(modelFields, comment) {
|
|
27
|
+
const modelName = modelFields[0].modelName;
|
|
28
|
+
const properties = modelFields.map((field) => {
|
|
29
|
+
return `${comment && field.comment.length > 0 ? `${field.comment.map((c) => ` /** ${c} */`).join("\n")}\n` : ""} ${field.fieldName}: ${field.validation ?? "{ type: 'unknown' as const }"},`;
|
|
30
|
+
}).join("\n");
|
|
31
|
+
const requiredFields = modelFields.filter((f) => f.isRequired).map((f) => f.fieldName);
|
|
32
|
+
return `export const ${modelName}Schema = {\n type: 'object' as const,\n properties: {\n${properties}\n },${requiredFields.length > 0 ? `\n required: [${requiredFields.map((f) => `'${f}'`).join(", ")}] as const,` : ""}\n additionalProperties: false,\n} as const`;
|
|
33
|
+
}
|
|
34
|
+
function makeAjvRelations(model, relProps, options) {
|
|
35
|
+
if (relProps.length === 0) return null;
|
|
36
|
+
const base = ` ...${model.name}Schema.properties,`;
|
|
37
|
+
const rels = relProps.map((r) => ` ${r.key}: ${r.isMany ? `{ type: 'array' as const, items: ${r.targetModel}Schema }` : `${r.targetModel}Schema`},`).join("\n");
|
|
38
|
+
const typeLine = options?.includeType ? `\n\nexport type ${model.name}Relations = FromSchema<typeof ${model.name}RelationsSchema>` : "";
|
|
39
|
+
return `export const ${model.name}RelationsSchema = {\n type: 'object' as const,\n properties: {\n${base}\n${rels}\n },\n additionalProperties: false,\n} as const${typeLine}`;
|
|
40
|
+
}
|
|
41
|
+
function ajv(models, type, comment, enums) {
|
|
42
|
+
return validationSchemas(models, type, comment, {
|
|
43
|
+
importStatement: type ? `import type { FromSchema } from 'json-schema-to-ts'` : "",
|
|
44
|
+
annotationPrefix: "@j.",
|
|
45
|
+
parseDocument: parseDocumentWithoutAnnotations,
|
|
46
|
+
extractValidation: makeValidationExtractor("@j."),
|
|
47
|
+
inferType: makeAjvInfer,
|
|
48
|
+
schemas: makeAjvSchemas,
|
|
49
|
+
typeMapping: PRISMA_TO_AJV,
|
|
50
|
+
enums,
|
|
51
|
+
formatEnum: makeAjvEnumExpression
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/generator/ajv/index.ts
|
|
57
|
+
const { generatorHandler } = pkg;
|
|
58
|
+
async function main(options) {
|
|
59
|
+
if (!(options.generator.isCustomOutput && options.generator.output?.value)) throw new Error("output is required for Hekireki-AJV. Please specify output in your generator config.");
|
|
60
|
+
const output = options.generator.output.value;
|
|
61
|
+
const resolved = path.extname(output) ? {
|
|
62
|
+
dir: path.dirname(output),
|
|
63
|
+
file: output
|
|
64
|
+
} : {
|
|
65
|
+
dir: output,
|
|
66
|
+
file: path.join(output, "index.ts")
|
|
67
|
+
};
|
|
68
|
+
const enableRelation = getBool(options.generator.config?.relation);
|
|
69
|
+
const fmtResult = await fmt([ajv(options.dmmf.datamodel.models, getBool(options.generator.config?.type), getBool(options.generator.config?.comment), options.dmmf.datamodel.enums), enableRelation ? makeRelationsOnly(options.dmmf, getBool(options.generator.config?.type), makeAjvRelations) : ""].filter(Boolean).join("\n\n"));
|
|
70
|
+
if (!fmtResult.ok) throw new Error(`Format error: ${fmtResult.error}`);
|
|
71
|
+
const mkdirResult = await mkdir(resolved.dir);
|
|
72
|
+
if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
|
|
73
|
+
const writeResult = await writeFile(resolved.file, fmtResult.value);
|
|
74
|
+
if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
|
|
75
|
+
}
|
|
76
|
+
generatorHandler({
|
|
77
|
+
onManifest() {
|
|
78
|
+
return {
|
|
79
|
+
defaultOutput: ".",
|
|
80
|
+
prettyName: "Hekireki-AJV"
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
onGenerate: main
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { main };
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as fmt } from "../../format-CzXgkLDe.js";
|
|
3
|
-
import {
|
|
4
|
-
import { n as validationSchemas, t as makeRelationsOnly } from "../../prisma-
|
|
3
|
+
import { c as parseDocumentWithoutAnnotations, d as mkdir, f as writeFile, l as schemaFromFields, s as makeValidationExtractor, t as getBool } from "../../utils-COHZyQue.js";
|
|
4
|
+
import { n as validationSchemas, t as makeRelationsOnly } from "../../prisma-ChsFqlYX.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import pkg from "@prisma/generator-helper";
|
|
7
7
|
|
|
8
8
|
//#region src/helper/arktype.ts
|
|
9
|
+
function makeArktypeInfer(modelName) {
|
|
10
|
+
return `export type ${modelName} = typeof ${modelName}Schema.infer`;
|
|
11
|
+
}
|
|
12
|
+
function makeArktypeSchema(modelName, fields) {
|
|
13
|
+
return `export const ${modelName}Schema = type({\n${fields}\n})`;
|
|
14
|
+
}
|
|
15
|
+
function makeArktypeProperties(fields, comment) {
|
|
16
|
+
return fields.map((field) => {
|
|
17
|
+
return `${comment && field.comment.length > 0 ? `${field.comment.map((c) => ` /** ${c} */`).join("\n")}\n` : ""} ${field.fieldName}: ${field.validation ?? "\"unknown\""},`;
|
|
18
|
+
}).join("\n");
|
|
19
|
+
}
|
|
20
|
+
function makeArktypeEnumExpression(values) {
|
|
21
|
+
return `"${values.map((v) => `'${v}'`).join(" | ")}"`;
|
|
22
|
+
}
|
|
9
23
|
const PRISMA_TO_ARKTYPE = {
|
|
10
24
|
String: "\"string\"",
|
|
11
25
|
Int: "\"number\"",
|
|
@@ -53,7 +67,7 @@ async function main(options) {
|
|
|
53
67
|
dir: output,
|
|
54
68
|
file: path.join(output, "index.ts")
|
|
55
69
|
};
|
|
56
|
-
const enableRelation =
|
|
70
|
+
const enableRelation = getBool(options.generator.config?.relation);
|
|
57
71
|
const fmtResult = await fmt([arktype(options.dmmf.datamodel.models, getBool(options.generator.config?.type), getBool(options.generator.config?.comment), options.dmmf.datamodel.enums), enableRelation ? makeRelationsOnly(options.dmmf, getBool(options.generator.config?.type), makeArktypeRelations) : ""].filter(Boolean).join("\n\n"));
|
|
58
72
|
if (!fmtResult.ok) throw new Error(`Format error: ${fmtResult.error}`);
|
|
59
73
|
const mkdirResult = await mkdir(resolved.dir);
|
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { d as mkdir, f as writeFile, n as getString, p as writeFileBinary, u as stripAnnotations } from "../../utils-COHZyQue.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import pkg from "@prisma/generator-helper";
|
|
5
5
|
import { Resvg } from "@resvg/resvg-js";
|
|
6
6
|
import { run } from "@softwaretechnik/dbml-renderer";
|
|
7
7
|
|
|
8
8
|
//#region src/helper/dbml.ts
|
|
9
|
+
function escapeNote(str) {
|
|
10
|
+
return str.replace(/'/g, "\\'");
|
|
11
|
+
}
|
|
12
|
+
function formatConstraints(constraints) {
|
|
13
|
+
return constraints.length > 0 ? ` [${constraints.join(", ")}]` : "";
|
|
14
|
+
}
|
|
15
|
+
function makeEnum(enumDef) {
|
|
16
|
+
return [
|
|
17
|
+
`Enum ${enumDef.name} {`,
|
|
18
|
+
...enumDef.values.map((v) => ` ${v}`),
|
|
19
|
+
"}"
|
|
20
|
+
].join("\n");
|
|
21
|
+
}
|
|
22
|
+
function makeRefName(ref) {
|
|
23
|
+
return ref.name ?? `${ref.fromTable}_${ref.fromColumn}_${ref.toTable}_${ref.toColumn}_fk`;
|
|
24
|
+
}
|
|
25
|
+
function combineKeys(keys) {
|
|
26
|
+
return keys.length > 1 ? `(${keys.join(", ")})` : keys[0];
|
|
27
|
+
}
|
|
9
28
|
function quote(value) {
|
|
10
29
|
return `'${escapeNote(value)}'`;
|
|
11
30
|
}
|
|
@@ -34,43 +53,37 @@ function makePrismaColumn(column) {
|
|
|
34
53
|
].filter((c) => Boolean(c));
|
|
35
54
|
return ` ${column.name} ${column.type}${formatConstraints(constraints)}`;
|
|
36
55
|
}
|
|
37
|
-
function resolveFieldType(field, models, mapToDbSchema) {
|
|
38
|
-
const baseType = mapToDbSchema ? models.find((m) => m.name === field.type)?.dbName ?? field.type : field.type;
|
|
39
|
-
return field.isList && !field.relationName ? `${baseType}[]` : baseType;
|
|
40
|
-
}
|
|
41
|
-
function resolveDefaultValue(field) {
|
|
42
|
-
const defaultDef = field.default;
|
|
43
|
-
if (defaultDef?.name === "autoincrement") return void 0;
|
|
44
|
-
if (defaultDef?.name === "now") return "`now()`";
|
|
45
|
-
if (field.hasDefaultValue && typeof field.default !== "object") return field.type === "String" || field.type === "Json" || field.kind === "enum" ? `'${field.default}'` : String(field.default);
|
|
46
|
-
}
|
|
47
56
|
function toDBMLColumn(field, models, mapToDbSchema) {
|
|
48
57
|
const defaultDef = field.default;
|
|
58
|
+
const baseType = mapToDbSchema ? models.find((m) => m.name === field.type)?.dbName ?? field.type : field.type;
|
|
59
|
+
const type = field.isList && !field.relationName ? `${baseType}[]` : baseType;
|
|
60
|
+
const defaultValue = (() => {
|
|
61
|
+
if (defaultDef?.name === "autoincrement") return void 0;
|
|
62
|
+
if (defaultDef?.name === "now") return "`now()`";
|
|
63
|
+
if (field.hasDefaultValue && typeof field.default !== "object") return field.type === "String" || field.type === "Json" || field.kind === "enum" ? `'${field.default}'` : String(field.default);
|
|
64
|
+
})();
|
|
49
65
|
return {
|
|
50
66
|
name: field.name,
|
|
51
|
-
type
|
|
67
|
+
type,
|
|
52
68
|
isPrimaryKey: field.isId,
|
|
53
69
|
isIncrement: defaultDef?.name === "autoincrement",
|
|
54
70
|
isUnique: field.isUnique,
|
|
55
71
|
isNotNull: field.isRequired && !field.isId,
|
|
56
|
-
defaultValue
|
|
72
|
+
defaultValue,
|
|
57
73
|
note: stripAnnotations(field.documentation)
|
|
58
74
|
};
|
|
59
75
|
}
|
|
60
|
-
function makeTableIndexes(model) {
|
|
61
|
-
return [...model.primaryKey?.fields && model.primaryKey.fields.length > 0 ? [{
|
|
62
|
-
columns: model.primaryKey.fields,
|
|
63
|
-
isPrimaryKey: true
|
|
64
|
-
}] : [], ...model.uniqueFields.filter((c) => c.length > 1).map((c) => ({
|
|
65
|
-
columns: c,
|
|
66
|
-
isUnique: true
|
|
67
|
-
}))];
|
|
68
|
-
}
|
|
69
76
|
function makeTables(models, mapToDbSchema = false) {
|
|
70
77
|
return models.map((model) => {
|
|
71
78
|
const modelName = mapToDbSchema && model.dbName ? model.dbName : model.name;
|
|
72
79
|
const columnLines = model.fields.map((field) => toDBMLColumn(field, models, mapToDbSchema)).map(makePrismaColumn).join("\n");
|
|
73
|
-
const indexes =
|
|
80
|
+
const indexes = [...model.primaryKey?.fields && model.primaryKey.fields.length > 0 ? [{
|
|
81
|
+
columns: model.primaryKey.fields,
|
|
82
|
+
isPrimaryKey: true
|
|
83
|
+
}] : [], ...model.uniqueFields.filter((c) => c.length > 1).map((c) => ({
|
|
84
|
+
columns: c,
|
|
85
|
+
isUnique: true
|
|
86
|
+
}))];
|
|
74
87
|
const indexBlock = indexes.length > 0 ? `\n\n indexes {\n${indexes.map(makeIndex).join("\n")}\n }` : "";
|
|
75
88
|
const strippedNote = stripAnnotations(model.documentation);
|
|
76
89
|
return `Table ${modelName} {\n${columnLines}${indexBlock}${strippedNote ? `\n\n Note: ${quote(escapeNote(strippedNote))}` : ""}\n}`;
|
|
@@ -84,14 +97,11 @@ function makeEnums(enums) {
|
|
|
84
97
|
});
|
|
85
98
|
});
|
|
86
99
|
}
|
|
87
|
-
function getRelationOperator(models, from, to) {
|
|
88
|
-
return (models.find((m) => m.name === to)?.fields.find((f) => f.type === from))?.isList ? ">" : "-";
|
|
89
|
-
}
|
|
90
100
|
function makeRelations(models, mapToDbSchema = false) {
|
|
91
101
|
return models.flatMap((model) => model.fields.filter((field) => field.relationName && field.relationToFields?.length && field.relationFromFields?.length).map((field) => {
|
|
92
102
|
const relationFrom = model.name;
|
|
93
103
|
const relationTo = field.type;
|
|
94
|
-
const operator =
|
|
104
|
+
const operator = (models.find((m) => m.name === relationTo)?.fields.find((f) => f.type === relationFrom))?.isList ? ">" : "-";
|
|
95
105
|
const relationFromName = mapToDbSchema && model.dbName ? model.dbName : model.name;
|
|
96
106
|
const relatedModel = models.find((m) => m.name === relationTo);
|
|
97
107
|
const relationToName = mapToDbSchema && relatedModel?.dbName ? relatedModel.dbName : relationTo;
|
|
@@ -118,25 +128,25 @@ function dbmlContent(datamodel, mapToDbSchema = false) {
|
|
|
118
128
|
...refs
|
|
119
129
|
].join("\n\n");
|
|
120
130
|
}
|
|
121
|
-
|
|
131
|
+
async function makeDbmlFile(outputDir, content, fileName) {
|
|
122
132
|
const writeResult = await writeFile(`${outputDir}/${fileName}`, content);
|
|
123
133
|
if (!writeResult.ok) return {
|
|
124
134
|
ok: false,
|
|
125
135
|
error: `Failed to write DBML: ${writeResult.error}`
|
|
126
136
|
};
|
|
127
137
|
return { ok: true };
|
|
128
|
-
}
|
|
129
|
-
|
|
138
|
+
}
|
|
139
|
+
async function makePng(outputDir, dbml, fileName) {
|
|
130
140
|
return makePngFile(`${outputDir}/${fileName}`, dbml);
|
|
131
|
-
}
|
|
132
|
-
|
|
141
|
+
}
|
|
142
|
+
async function makePngFile(outputPath, dbml) {
|
|
133
143
|
const writeResult = await writeFileBinary(outputPath, new Resvg(run(dbml, "svg"), { font: { loadSystemFonts: true } }).render().asPng());
|
|
134
144
|
if (!writeResult.ok) return {
|
|
135
145
|
ok: false,
|
|
136
146
|
error: `Failed to write PNG: ${writeResult.error}`
|
|
137
147
|
};
|
|
138
148
|
return { ok: true };
|
|
139
|
-
}
|
|
149
|
+
}
|
|
140
150
|
|
|
141
151
|
//#endregion
|
|
142
152
|
//#region src/generator/dbml/index.ts
|