hekireki 0.7.1 → 0.7.3
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 +136 -3
- package/dist/{format-CzXgkLDe.js → format-DwiYldCm.js} +2 -8
- package/dist/generator/ajv/index.js +6 -9
- package/dist/generator/arktype/index.js +6 -9
- package/dist/generator/dbml/index.js +10 -32
- package/dist/generator/docs/index.js +33 -24
- package/dist/generator/drizzle/index.js +126 -43
- package/dist/generator/ecto/index.js +12 -18
- package/dist/generator/effect/index.js +6 -9
- package/dist/generator/gorm/index.d.ts +6 -0
- package/dist/generator/gorm/index.js +363 -0
- package/dist/generator/mermaid-er/index.js +3 -5
- package/dist/generator/sea-orm/index.d.ts +6 -0
- package/dist/generator/sea-orm/index.js +433 -0
- package/dist/generator/sqlalchemy/index.d.ts +6 -0
- package/dist/generator/sqlalchemy/index.js +451 -0
- package/dist/generator/typebox/index.js +6 -9
- package/dist/generator/valibot/index.js +6 -9
- package/dist/generator/zod/index.js +6 -9
- package/dist/{prisma-ChsFqlYX.js → prisma-DHllEbMR.js} +2 -2
- package/dist/{utils-COHZyQue.js → utils-0tE5jzYR.js} +3 -36
- package/package.json +40 -34
package/README.md
CHANGED
|
@@ -2,20 +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
|
|
13
15
|
- 📦 Automatically generates [TypeBox](https://github.com/sinclairzx81/typebox) schemas from your Prisma schema
|
|
14
16
|
- 📋 Automatically generates [AJV](https://ajv.js.org/)-compatible JSON Schema objects from your Prisma schema
|
|
17
|
+
|
|
18
|
+
### ORM / Schema Generation (Multi-Language)
|
|
19
|
+
|
|
15
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
|
+
|
|
16
28
|
- 📊 Creates [Mermaid](https://mermaid.js.org/) ER diagrams with PK/FK markers
|
|
17
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`)
|
|
18
|
-
- 🧪 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`
|
|
19
30
|
|
|
20
31
|
## Installation
|
|
21
32
|
|
|
@@ -82,6 +93,23 @@ generator Hekireki-Drizzle {
|
|
|
82
93
|
provider = "hekireki-drizzle"
|
|
83
94
|
}
|
|
84
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
|
+
|
|
85
113
|
generator Hekireki-Ecto {
|
|
86
114
|
provider = "hekireki-ecto"
|
|
87
115
|
output = "./ecto"
|
|
@@ -520,6 +548,91 @@ defmodule DBSchema.Post do
|
|
|
520
548
|
end
|
|
521
549
|
```
|
|
522
550
|
|
|
551
|
+
### SQLAlchemy
|
|
552
|
+
|
|
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
|
+
```
|
|
635
|
+
|
|
523
636
|
### DBML
|
|
524
637
|
|
|
525
638
|
```dbml
|
|
@@ -634,7 +747,27 @@ generator Hekireki-ER {
|
|
|
634
747
|
output = "./mermaid-er" // Output path (default: ./mermaid-er/ER.md)
|
|
635
748
|
}
|
|
636
749
|
|
|
637
|
-
//
|
|
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)
|
|
638
771
|
generator Hekireki-Ecto {
|
|
639
772
|
provider = "hekireki-ecto"
|
|
640
773
|
output = "./ecto" // Output directory (default: ./ecto/)
|
|
@@ -7,14 +7,8 @@ async function fmt(input) {
|
|
|
7
7
|
singleQuote: true,
|
|
8
8
|
semi: false
|
|
9
9
|
});
|
|
10
|
-
if (errors.length > 0)
|
|
11
|
-
|
|
12
|
-
error: errors.map((e) => e.message).join("\n")
|
|
13
|
-
};
|
|
14
|
-
return {
|
|
15
|
-
ok: true,
|
|
16
|
-
value: code
|
|
17
|
-
};
|
|
10
|
+
if (errors.length > 0) throw new Error(errors.map((e) => e.message).join("\n"));
|
|
11
|
+
return code;
|
|
18
12
|
}
|
|
19
13
|
|
|
20
14
|
//#endregion
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as fmt } from "../../format-
|
|
3
|
-
import { c as parseDocumentWithoutAnnotations, d as mkdir, f as writeFile, s as makeValidationExtractor, t as getBool } from "../../utils-
|
|
4
|
-
import { n as validationSchemas, t as makeRelationsOnly } from "../../prisma-
|
|
2
|
+
import { t as fmt } from "../../format-DwiYldCm.js";
|
|
3
|
+
import { c as parseDocumentWithoutAnnotations, d as mkdir, f as writeFile, s as makeValidationExtractor, t as getBool } from "../../utils-0tE5jzYR.js";
|
|
4
|
+
import { n as validationSchemas, t as makeRelationsOnly } from "../../prisma-DHllEbMR.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import pkg from "@prisma/generator-helper";
|
|
7
7
|
|
|
@@ -66,12 +66,9 @@ async function main(options) {
|
|
|
66
66
|
file: path.join(output, "index.ts")
|
|
67
67
|
};
|
|
68
68
|
const enableRelation = getBool(options.generator.config?.relation);
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
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}`);
|
|
69
|
+
const code = 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
|
+
await mkdir(resolved.dir);
|
|
71
|
+
await writeFile(resolved.file, code);
|
|
75
72
|
}
|
|
76
73
|
generatorHandler({
|
|
77
74
|
onManifest() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as fmt } from "../../format-
|
|
3
|
-
import { c as parseDocumentWithoutAnnotations, d as mkdir, f as writeFile, l as schemaFromFields, s as makeValidationExtractor, t as getBool } from "../../utils-
|
|
4
|
-
import { n as validationSchemas, t as makeRelationsOnly } from "../../prisma-
|
|
2
|
+
import { t as fmt } from "../../format-DwiYldCm.js";
|
|
3
|
+
import { c as parseDocumentWithoutAnnotations, d as mkdir, f as writeFile, l as schemaFromFields, s as makeValidationExtractor, t as getBool } from "../../utils-0tE5jzYR.js";
|
|
4
|
+
import { n as validationSchemas, t as makeRelationsOnly } from "../../prisma-DHllEbMR.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import pkg from "@prisma/generator-helper";
|
|
7
7
|
|
|
@@ -68,12 +68,9 @@ async function main(options) {
|
|
|
68
68
|
file: path.join(output, "index.ts")
|
|
69
69
|
};
|
|
70
70
|
const enableRelation = getBool(options.generator.config?.relation);
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
|
|
75
|
-
const writeResult = await writeFile(resolved.file, fmtResult.value);
|
|
76
|
-
if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
|
|
71
|
+
const code = 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"));
|
|
72
|
+
await mkdir(resolved.dir);
|
|
73
|
+
await writeFile(resolved.file, code);
|
|
77
74
|
}
|
|
78
75
|
generatorHandler({
|
|
79
76
|
onManifest() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { d as mkdir, f as writeFile, n as getString, p as writeFileBinary, u as stripAnnotations } from "../../utils-
|
|
2
|
+
import { d as mkdir, f as writeFile, n as getString, p as writeFileBinary, u as stripAnnotations } from "../../utils-0tE5jzYR.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import pkg from "@prisma/generator-helper";
|
|
5
5
|
import { Resvg } from "@resvg/resvg-js";
|
|
@@ -129,23 +129,13 @@ function dbmlContent(datamodel, mapToDbSchema = false) {
|
|
|
129
129
|
].join("\n\n");
|
|
130
130
|
}
|
|
131
131
|
async function makeDbmlFile(outputDir, content, fileName) {
|
|
132
|
-
|
|
133
|
-
if (!writeResult.ok) return {
|
|
134
|
-
ok: false,
|
|
135
|
-
error: `Failed to write DBML: ${writeResult.error}`
|
|
136
|
-
};
|
|
137
|
-
return { ok: true };
|
|
132
|
+
await writeFile(`${outputDir}/${fileName}`, content);
|
|
138
133
|
}
|
|
139
134
|
async function makePng(outputDir, dbml, fileName) {
|
|
140
|
-
|
|
135
|
+
await makePngFile(`${outputDir}/${fileName}`, dbml);
|
|
141
136
|
}
|
|
142
137
|
async function makePngFile(outputPath, dbml) {
|
|
143
|
-
|
|
144
|
-
if (!writeResult.ok) return {
|
|
145
|
-
ok: false,
|
|
146
|
-
error: `Failed to write PNG: ${writeResult.error}`
|
|
147
|
-
};
|
|
148
|
-
return { ok: true };
|
|
138
|
+
await writeFileBinary(outputPath, new Resvg(run(dbml, "svg"), { font: { loadSystemFonts: true } }).render().asPng());
|
|
149
139
|
}
|
|
150
140
|
|
|
151
141
|
//#endregion
|
|
@@ -158,15 +148,9 @@ async function main(options) {
|
|
|
158
148
|
const mapToDbSchema = getString(config?.mapToDbSchema) !== "false";
|
|
159
149
|
const content = dbmlContent(options.dmmf.datamodel, mapToDbSchema);
|
|
160
150
|
if (output.endsWith(".png") || output.endsWith(".dbml")) {
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
const pngResult = await makePngFile(output, content);
|
|
165
|
-
if (!pngResult.ok) throw new Error(pngResult.error);
|
|
166
|
-
} else {
|
|
167
|
-
const dbmlResult = await writeFile(output, content);
|
|
168
|
-
if (!dbmlResult.ok) throw new Error(`Failed to write DBML: ${dbmlResult.error}`);
|
|
169
|
-
}
|
|
151
|
+
await mkdir(path.dirname(output));
|
|
152
|
+
if (output.endsWith(".png")) await makePngFile(output, content);
|
|
153
|
+
else await writeFile(output, content);
|
|
170
154
|
} else {
|
|
171
155
|
const resolved = path.extname(output) ? {
|
|
172
156
|
dir: path.dirname(output),
|
|
@@ -175,16 +159,10 @@ async function main(options) {
|
|
|
175
159
|
dir: output,
|
|
176
160
|
file: path.join(output, "schema.dbml")
|
|
177
161
|
};
|
|
178
|
-
|
|
179
|
-
if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
|
|
162
|
+
await mkdir(resolved.dir);
|
|
180
163
|
const fileName = path.basename(resolved.file);
|
|
181
|
-
if (fileName.endsWith(".png"))
|
|
182
|
-
|
|
183
|
-
if (!pngResult.ok) throw new Error(pngResult.error);
|
|
184
|
-
} else {
|
|
185
|
-
const dbmlResult = await makeDbmlFile(resolved.dir, content, fileName);
|
|
186
|
-
if (!dbmlResult.ok) throw new Error(dbmlResult.error);
|
|
187
|
-
}
|
|
164
|
+
if (fileName.endsWith(".png")) await makePng(resolved.dir, content, fileName);
|
|
165
|
+
else await makeDbmlFile(resolved.dir, content, fileName);
|
|
188
166
|
}
|
|
189
167
|
}
|
|
190
168
|
generatorHandler({
|
|
@@ -33,21 +33,6 @@ const transformDMMF = (dmmf) => {
|
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
//#endregion
|
|
37
|
-
//#region src/generator/docs/generator/helpers.ts
|
|
38
|
-
const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
|
|
39
|
-
const lowerCase = (name) => name.substring(0, 1).toLowerCase() + name.substring(1);
|
|
40
|
-
const primitiveTypes = [
|
|
41
|
-
"String",
|
|
42
|
-
"Boolean",
|
|
43
|
-
"Int",
|
|
44
|
-
"Float",
|
|
45
|
-
"Json",
|
|
46
|
-
"DateTime",
|
|
47
|
-
"Null"
|
|
48
|
-
];
|
|
49
|
-
const isScalarType = (type) => primitiveTypes.includes(type);
|
|
50
|
-
|
|
51
36
|
//#endregion
|
|
52
37
|
//#region src/generator/docs/styles.ts
|
|
53
38
|
const globalCss = css`
|
|
@@ -76,16 +61,24 @@ const globalCss = css`
|
|
|
76
61
|
--code-bg: #374151;
|
|
77
62
|
--code-color: #e5e7eb;
|
|
78
63
|
}
|
|
79
|
-
* {
|
|
64
|
+
* {
|
|
65
|
+
box-sizing: border-box;
|
|
66
|
+
}
|
|
80
67
|
body {
|
|
81
68
|
margin: 0;
|
|
82
|
-
font-family:
|
|
69
|
+
font-family:
|
|
70
|
+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
83
71
|
background-color: var(--bg-primary);
|
|
84
72
|
color: var(--text-primary);
|
|
85
73
|
min-height: 100vh;
|
|
86
74
|
}
|
|
87
|
-
a {
|
|
88
|
-
|
|
75
|
+
a {
|
|
76
|
+
color: var(--link-color);
|
|
77
|
+
text-decoration: none;
|
|
78
|
+
}
|
|
79
|
+
a:hover {
|
|
80
|
+
text-decoration: underline;
|
|
81
|
+
}
|
|
89
82
|
}
|
|
90
83
|
`;
|
|
91
84
|
const containerClass = css`
|
|
@@ -218,7 +211,8 @@ const codeBlockClass = css`
|
|
|
218
211
|
padding: 1em;
|
|
219
212
|
border-radius: 0.5em;
|
|
220
213
|
overflow: auto;
|
|
221
|
-
font-family:
|
|
214
|
+
font-family:
|
|
215
|
+
'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
222
216
|
font-size: 14px;
|
|
223
217
|
line-height: 1.5;
|
|
224
218
|
margin: 0;
|
|
@@ -261,6 +255,21 @@ const ml4Class = css`
|
|
|
261
255
|
margin-left: 1rem;
|
|
262
256
|
`;
|
|
263
257
|
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/generator/docs/generator/helpers.ts
|
|
260
|
+
const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
|
|
261
|
+
const lowerCase = (name) => name.substring(0, 1).toLowerCase() + name.substring(1);
|
|
262
|
+
const primitiveTypes = [
|
|
263
|
+
"String",
|
|
264
|
+
"Boolean",
|
|
265
|
+
"Int",
|
|
266
|
+
"Float",
|
|
267
|
+
"Json",
|
|
268
|
+
"DateTime",
|
|
269
|
+
"Null"
|
|
270
|
+
];
|
|
271
|
+
const isScalarType = (type) => primitiveTypes.includes(type);
|
|
272
|
+
|
|
264
273
|
//#endregion
|
|
265
274
|
//#region src/generator/docs/generator/apitypes.tsx
|
|
266
275
|
const TypeRefLink = ({ typeRef, kind }) => {
|
|
@@ -940,14 +949,14 @@ const MainContent = ({ models, types }) => /* @__PURE__ */ jsxs("div", {
|
|
|
940
949
|
class: mainContentClass,
|
|
941
950
|
children: [models, types]
|
|
942
951
|
});
|
|
943
|
-
const generateHTML =
|
|
944
|
-
return
|
|
952
|
+
const generateHTML = (data) => {
|
|
953
|
+
return `<!DOCTYPE html>${(/* @__PURE__ */ jsx(Layout, { children: /* @__PURE__ */ jsxs("div", {
|
|
945
954
|
class: containerClass,
|
|
946
955
|
children: [/* @__PURE__ */ jsx(Sidebar, { toc: createTOC(data) }), /* @__PURE__ */ jsx(MainContent, {
|
|
947
956
|
models: createModels(data),
|
|
948
957
|
types: createTypes(data)
|
|
949
958
|
})]
|
|
950
|
-
}) })).toString()
|
|
959
|
+
}) })).toString()}`;
|
|
951
960
|
};
|
|
952
961
|
|
|
953
962
|
//#endregion
|
|
@@ -963,7 +972,7 @@ generatorHandler({
|
|
|
963
972
|
async onGenerate(options) {
|
|
964
973
|
if (!options.generator.isCustomOutput || !options.generator.output?.value) throw new Error("output is required for Hekireki-Docs. Please specify output in your generator config.");
|
|
965
974
|
const output = options.generator.output.value;
|
|
966
|
-
const html =
|
|
975
|
+
const html = generateHTML(transformDMMF(options.dmmf));
|
|
967
976
|
await fs.mkdir(output, { recursive: true });
|
|
968
977
|
await fs.writeFile(path.join(output, "index.html"), html);
|
|
969
978
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as fmt } from "../../format-
|
|
3
|
-
import { d as mkdir, f as writeFile, o as makeSnakeCase } from "../../utils-
|
|
2
|
+
import { t as fmt } from "../../format-DwiYldCm.js";
|
|
3
|
+
import { d as mkdir, f as writeFile, o as makeSnakeCase } from "../../utils-0tE5jzYR.js";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import pkg from "@prisma/generator-helper";
|
|
6
6
|
|
|
@@ -23,7 +23,7 @@ const MYSQL_SCALAR_MAP = {
|
|
|
23
23
|
Float: "double()",
|
|
24
24
|
Decimal: "decimal()",
|
|
25
25
|
Boolean: "boolean()",
|
|
26
|
-
DateTime: "datetime()",
|
|
26
|
+
DateTime: "datetime({ fsp: 3 })",
|
|
27
27
|
Json: "json()",
|
|
28
28
|
Bytes: "binary()"
|
|
29
29
|
};
|
|
@@ -34,7 +34,7 @@ const SQLITE_SCALAR_MAP = {
|
|
|
34
34
|
Float: "real()",
|
|
35
35
|
Decimal: "numeric()",
|
|
36
36
|
Boolean: "integer({ mode: 'boolean' })",
|
|
37
|
-
DateTime: "integer({ mode: '
|
|
37
|
+
DateTime: "integer({ mode: 'timestamp_ms' })",
|
|
38
38
|
Json: "text({ mode: 'json' })",
|
|
39
39
|
Bytes: "blob()"
|
|
40
40
|
};
|
|
@@ -88,8 +88,8 @@ function mysqlNativeType(name, args) {
|
|
|
88
88
|
}
|
|
89
89
|
case "Date": return "date()";
|
|
90
90
|
case "Time": return args[0] ? `time({ fsp: ${args[0]} })` : "time()";
|
|
91
|
-
case "DateTime": return
|
|
92
|
-
case "Timestamp": return
|
|
91
|
+
case "DateTime": return `datetime({ fsp: ${args[0] ?? 3} })`;
|
|
92
|
+
case "Timestamp": return `timestamp({ fsp: ${args[0] ?? 3} })`;
|
|
93
93
|
case "Json": return "json()";
|
|
94
94
|
case "Binary": return args[0] ? `binary({ length: ${args[0]} })` : "binary()";
|
|
95
95
|
case "VarBinary": return args[0] ? `varbinary({ length: ${args[0]} })` : "varbinary()";
|
|
@@ -169,36 +169,117 @@ function makeColumnExpr(field, provider, imports, enums) {
|
|
|
169
169
|
const rest = baseExpr.slice(parenIdx + 1);
|
|
170
170
|
return rest === ")" ? `${baseFnName}('${colName}')` : `${baseFnName}('${colName}', ${rest}`;
|
|
171
171
|
}
|
|
172
|
-
function
|
|
173
|
-
if (dflt === void 0 || dflt === null) return
|
|
172
|
+
function resolveDefaultValue(dflt, fieldType, provider) {
|
|
173
|
+
if (dflt === void 0 || dflt === null) return {
|
|
174
|
+
chain: "",
|
|
175
|
+
needsSql: false,
|
|
176
|
+
needsCuid: false
|
|
177
|
+
};
|
|
174
178
|
if (isFieldDefault(dflt)) switch (dflt.name) {
|
|
175
|
-
case "autoincrement": return
|
|
179
|
+
case "autoincrement": return {
|
|
180
|
+
chain: "",
|
|
181
|
+
needsSql: false,
|
|
182
|
+
needsCuid: false
|
|
183
|
+
};
|
|
176
184
|
case "now":
|
|
177
|
-
if (provider === "sqlite") {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
185
|
+
if (provider === "sqlite") return {
|
|
186
|
+
chain: ".default(sql`(unixepoch() * 1000)`)",
|
|
187
|
+
needsSql: true,
|
|
188
|
+
needsCuid: false
|
|
189
|
+
};
|
|
190
|
+
if (provider === "mysql") return {
|
|
191
|
+
chain: ".default(sql`CURRENT_TIMESTAMP(3)`)",
|
|
192
|
+
needsSql: true,
|
|
193
|
+
needsCuid: false
|
|
194
|
+
};
|
|
195
|
+
return {
|
|
196
|
+
chain: ".defaultNow()",
|
|
197
|
+
needsSql: false,
|
|
198
|
+
needsCuid: false
|
|
199
|
+
};
|
|
200
|
+
case "uuid": return {
|
|
201
|
+
chain: ".$defaultFn(() => crypto.randomUUID())",
|
|
202
|
+
needsSql: false,
|
|
203
|
+
needsCuid: false
|
|
204
|
+
};
|
|
205
|
+
case "cuid": return {
|
|
206
|
+
chain: ".$defaultFn(() => createId())",
|
|
207
|
+
needsSql: false,
|
|
208
|
+
needsCuid: true
|
|
209
|
+
};
|
|
190
210
|
case "dbgenerated":
|
|
191
|
-
if (typeof dflt.args[0] === "string") {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
211
|
+
if (typeof dflt.args[0] === "string") return {
|
|
212
|
+
chain: `.default(sql\`${dflt.args[0]}\`)`,
|
|
213
|
+
needsSql: true,
|
|
214
|
+
needsCuid: false
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
chain: "",
|
|
218
|
+
needsSql: false,
|
|
219
|
+
needsCuid: false
|
|
220
|
+
};
|
|
221
|
+
default: return {
|
|
222
|
+
chain: "",
|
|
223
|
+
needsSql: false,
|
|
224
|
+
needsCuid: false
|
|
225
|
+
};
|
|
197
226
|
}
|
|
198
|
-
if (typeof dflt === "string") return
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
227
|
+
if (typeof dflt === "string") return {
|
|
228
|
+
chain: `.default('${dflt}')`,
|
|
229
|
+
needsSql: false,
|
|
230
|
+
needsCuid: false
|
|
231
|
+
};
|
|
232
|
+
if (typeof dflt === "number") return {
|
|
233
|
+
chain: fieldType === "Decimal" ? `.default('${dflt}')` : `.default(${dflt})`,
|
|
234
|
+
needsSql: false,
|
|
235
|
+
needsCuid: false
|
|
236
|
+
};
|
|
237
|
+
if (typeof dflt === "boolean") return {
|
|
238
|
+
chain: `.default(${dflt})`,
|
|
239
|
+
needsSql: false,
|
|
240
|
+
needsCuid: false
|
|
241
|
+
};
|
|
242
|
+
return {
|
|
243
|
+
chain: "",
|
|
244
|
+
needsSql: false,
|
|
245
|
+
needsCuid: false
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function resolveUpdatedAtDefault(provider) {
|
|
249
|
+
if (provider === "sqlite") return {
|
|
250
|
+
chain: ".default(sql`(unixepoch() * 1000)`)",
|
|
251
|
+
needsSql: true
|
|
252
|
+
};
|
|
253
|
+
if (provider === "mysql") return {
|
|
254
|
+
chain: ".default(sql`CURRENT_TIMESTAMP(3)`)",
|
|
255
|
+
needsSql: true
|
|
256
|
+
};
|
|
257
|
+
return {
|
|
258
|
+
chain: ".defaultNow()",
|
|
259
|
+
needsSql: false
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function makeDefaultChain(dflt, fieldType, provider, imports) {
|
|
263
|
+
const result = resolveDefaultValue(dflt, fieldType, provider);
|
|
264
|
+
if (result.needsSql) imports.addOrm("sql");
|
|
265
|
+
if (result.needsCuid) imports.addExternal("@paralleldrive/cuid2", "createId");
|
|
266
|
+
return result.chain;
|
|
267
|
+
}
|
|
268
|
+
const PRISMA_ACTION_MAP = {
|
|
269
|
+
Cascade: "cascade",
|
|
270
|
+
SetNull: "set null",
|
|
271
|
+
Restrict: "restrict",
|
|
272
|
+
NoAction: "no action",
|
|
273
|
+
SetDefault: "set default"
|
|
274
|
+
};
|
|
275
|
+
function makeFkReference(field, model) {
|
|
276
|
+
const relField = model.fields.find((f) => f.kind === "object" && f.relationFromFields && f.relationFromFields.includes(field.name));
|
|
277
|
+
if (!(relField?.relationFromFields && relField.relationToFields)) return "";
|
|
278
|
+
const targetVar = toCamelCase(relField.type);
|
|
279
|
+
const toCol = relField.relationToFields[0] ?? "id";
|
|
280
|
+
const onDelete = relField.relationOnDelete;
|
|
281
|
+
const drizzleAction = onDelete ? PRISMA_ACTION_MAP[onDelete] : void 0;
|
|
282
|
+
return `.references(() => ${targetVar}.${toCol}${drizzleAction ? `, { onDelete: '${drizzleAction}' }` : ""})`;
|
|
202
283
|
}
|
|
203
284
|
function makeColumn(field, model, provider, imports, enums) {
|
|
204
285
|
if (field.kind === "object") return null;
|
|
@@ -210,24 +291,29 @@ function makeColumn(field, model, provider, imports, enums) {
|
|
|
210
291
|
field.isId && !hasCompositePK ? isAutoincrement && provider === "sqlite" ? ".primaryKey({ autoIncrement: true })" : ".primaryKey()" : "",
|
|
211
292
|
field.isRequired && !field.isId && !(isAutoincrement && provider === "postgresql") ? ".notNull()" : "",
|
|
212
293
|
field.isUnique ? ".unique()" : "",
|
|
213
|
-
|
|
294
|
+
makeFkReference(field, model),
|
|
295
|
+
isAutoincrement ? provider === "mysql" ? ".autoincrement()" : "" : field.isUpdatedAt && (field.default === void 0 || field.default === null) ? (() => {
|
|
296
|
+
const r = resolveUpdatedAtDefault(provider);
|
|
297
|
+
if (r.needsSql) imports.addOrm("sql");
|
|
298
|
+
return r.chain;
|
|
299
|
+
})() : makeDefaultChain(field.default, field.type, provider, imports),
|
|
214
300
|
field.isUpdatedAt ? ".$onUpdate(() => new Date())" : "",
|
|
215
301
|
field.isList && field.kind === "scalar" && provider === "postgresql" ? ".array()" : ""
|
|
216
302
|
].join("");
|
|
217
303
|
return `${field.name}: ${colExpr}${chain}`;
|
|
218
304
|
}
|
|
219
|
-
function makeCompositeConstraints(model, imports, indexes) {
|
|
305
|
+
function makeCompositeConstraints(model, imports, indexes, tableName) {
|
|
220
306
|
const pkLine = model.primaryKey ? (() => {
|
|
221
307
|
imports.addCore("primaryKey");
|
|
222
308
|
return `primaryKey({ columns: [${model.primaryKey.fields.map((f) => `table.${f}`).join(", ")}] })`;
|
|
223
309
|
})() : null;
|
|
224
|
-
const uniqueLines = model.uniqueFields.map((fields) => {
|
|
310
|
+
const uniqueLines = model.uniqueFields.filter((fields) => fields.length > 1).map((fields) => {
|
|
225
311
|
imports.addCore("unique");
|
|
226
312
|
return `unique().on(${fields.map((f) => `table.${f}`).join(", ")})`;
|
|
227
313
|
});
|
|
228
314
|
const indexLines = indexes.filter((idx) => idx.model === model.name && (idx.type === "normal" || idx.type === "fulltext")).map((idx) => {
|
|
229
315
|
imports.addCore("index");
|
|
230
|
-
return `index('${idx.dbName ?? idx.name ?? `idx_${idx.fields.map((f) => f.name).join("_")}`}').on(${idx.fields.map((f) => `table.${f.name}`).join(", ")})`;
|
|
316
|
+
return `index('${idx.dbName ?? idx.name ?? `idx_${tableName}_${idx.fields.map((f) => f.name).join("_")}`}').on(${idx.fields.map((f) => `table.${f.name}`).join(", ")})`;
|
|
231
317
|
});
|
|
232
318
|
const all = [
|
|
233
319
|
pkLine,
|
|
@@ -242,7 +328,7 @@ function makeTable(model, provider, imports, enums, indexes) {
|
|
|
242
328
|
const varName = toCamelCase(model.name);
|
|
243
329
|
const tableName = model.dbName ?? makeSnakeCase(model.name);
|
|
244
330
|
const columns = model.fields.map((field) => makeColumn(field, model, provider, imports, enums)).filter((c) => c !== null).join(", ");
|
|
245
|
-
const constraints = makeCompositeConstraints(model, imports, indexes);
|
|
331
|
+
const constraints = makeCompositeConstraints(model, imports, indexes, tableName);
|
|
246
332
|
return constraints ? `export const ${varName} = ${tableFunc}('${tableName}', { ${columns} }, (table) => [${constraints}])` : `export const ${varName} = ${tableFunc}('${tableName}', { ${columns} })`;
|
|
247
333
|
}
|
|
248
334
|
function makeRelationField(field, model, _models, relFields) {
|
|
@@ -312,12 +398,9 @@ async function main(options) {
|
|
|
312
398
|
file: path.join(output, "schema.ts")
|
|
313
399
|
};
|
|
314
400
|
const provider = options.datasources[0]?.activeProvider ?? "postgresql";
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
|
|
319
|
-
const writeResult = await writeFile(resolved.file, fmtResult.value);
|
|
320
|
-
if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
|
|
401
|
+
const formatted = await fmt(drizzleSchema(options.dmmf.datamodel, provider, options.dmmf.datamodel.indexes));
|
|
402
|
+
await mkdir(resolved.dir);
|
|
403
|
+
await writeFile(resolved.file, formatted);
|
|
321
404
|
}
|
|
322
405
|
generatorHandler({
|
|
323
406
|
onManifest() {
|