hekireki 0.6.3 โ 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -6
- 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 +208 -30
- package/dist/generator/effect/index.js +17 -3
- package/dist/generator/mermaid-er/index.js +6 -12
- 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-DKvlcO1B.js โ prisma-ChsFqlYX.js} +6 -6
- package/dist/utils-COHZyQue.js +116 -0
- package/package.json +10 -2
- package/dist/utils-bU9015ai.js +0 -279
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Hekireki
|
|
4
4
|
|
|
5
|
-
**[Hekireki](https://www.npmjs.com/package/hekireki)** is a tool that generates validation schemas for Zod, Valibot, ArkType, and
|
|
5
|
+
**[Hekireki](https://www.npmjs.com/package/hekireki)** is a tool that generates validation schemas for Zod, Valibot, ArkType, Effect Schema, TypeBox, and AJV (JSON Schema), as well as [Drizzle ORM](https://orm.drizzle.team/) schemas and ER diagrams, from [Prisma](https://www.prisma.io/) schemas annotated with comments.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
- ๐ค Automatically generates [Valibot](https://valibot.dev/) schemas from your Prisma schema
|
|
11
11
|
- ๐น Automatically generates [ArkType](https://arktype.io/) schemas from your Prisma schema
|
|
12
12
|
- โก Automatically generates [Effect Schema](https://effect.website/docs/schema/introduction/) from your Prisma schema
|
|
13
|
+
- ๐ฆ Automatically generates [TypeBox](https://github.com/sinclairzx81/typebox) schemas from your Prisma schema
|
|
14
|
+
- ๐ Automatically generates [AJV](https://ajv.js.org/)-compatible JSON Schema objects from your Prisma schema
|
|
13
15
|
- ๐๏ธ Automatically generates [Drizzle ORM](https://orm.drizzle.team/) table schemas and relations from your Prisma schema
|
|
14
16
|
- ๐ Creates [Mermaid](https://mermaid.js.org/) ER diagrams with PK/FK markers
|
|
15
17
|
- ๐ 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
|
|
17
|
-
โ ๏ธ Foreign key constraints are **not** included โ manage relationships in your application logic
|
|
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`
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
20
21
|
|
|
@@ -63,6 +64,20 @@ generator Hekireki-Effect {
|
|
|
63
64
|
relation = true
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
generator Hekireki-TypeBox {
|
|
68
|
+
provider = "hekireki-typebox"
|
|
69
|
+
type = true
|
|
70
|
+
comment = true
|
|
71
|
+
relation = true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
generator Hekireki-AJV {
|
|
75
|
+
provider = "hekireki-ajv"
|
|
76
|
+
type = true
|
|
77
|
+
comment = true
|
|
78
|
+
relation = true
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
generator Hekireki-Drizzle {
|
|
67
82
|
provider = "hekireki-drizzle"
|
|
68
83
|
}
|
|
@@ -89,12 +104,16 @@ model User {
|
|
|
89
104
|
/// @v.pipe(v.string(), v.uuid())
|
|
90
105
|
/// @a."string.uuid"
|
|
91
106
|
/// @e.Schema.UUID
|
|
107
|
+
/// @t.Type.String({ format: 'uuid' })
|
|
108
|
+
/// @j.{ type: 'string' as const, format: 'uuid' as const }
|
|
92
109
|
id String @id @default(uuid())
|
|
93
110
|
/// Display name
|
|
94
111
|
/// @z.string().min(1).max(50)
|
|
95
112
|
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(50))
|
|
96
113
|
/// @a."1 <= string <= 50"
|
|
97
114
|
/// @e.Schema.String.pipe(Schema.minLength(1), Schema.maxLength(50))
|
|
115
|
+
/// @t.Type.String({ minLength: 1, maxLength: 50 })
|
|
116
|
+
/// @j.{ type: 'string' as const, minLength: 1, maxLength: 50 }
|
|
98
117
|
name String
|
|
99
118
|
/// One-to-many relation to Post
|
|
100
119
|
posts Post[]
|
|
@@ -106,24 +125,32 @@ model Post {
|
|
|
106
125
|
/// @v.pipe(v.string(), v.uuid())
|
|
107
126
|
/// @a."string.uuid"
|
|
108
127
|
/// @e.Schema.UUID
|
|
128
|
+
/// @t.Type.String({ format: 'uuid' })
|
|
129
|
+
/// @j.{ type: 'string' as const, format: 'uuid' as const }
|
|
109
130
|
id String @id @default(uuid())
|
|
110
131
|
/// Article title
|
|
111
132
|
/// @z.string().min(1).max(100)
|
|
112
133
|
/// @v.pipe(v.string(), v.minLength(1), v.maxLength(100))
|
|
113
134
|
/// @a."1 <= string <= 100"
|
|
114
135
|
/// @e.Schema.String.pipe(Schema.minLength(1), Schema.maxLength(100))
|
|
136
|
+
/// @t.Type.String({ minLength: 1, maxLength: 100 })
|
|
137
|
+
/// @j.{ type: 'string' as const, minLength: 1, maxLength: 100 }
|
|
115
138
|
title String
|
|
116
139
|
/// Body content (no length limit)
|
|
117
140
|
/// @z.string()
|
|
118
141
|
/// @v.string()
|
|
119
142
|
/// @a."string"
|
|
120
143
|
/// @e.Schema.String
|
|
144
|
+
/// @t.Type.String()
|
|
145
|
+
/// @j.{ type: 'string' as const }
|
|
121
146
|
content String
|
|
122
147
|
/// Foreign key referencing User.id
|
|
123
148
|
/// @z.uuid()
|
|
124
149
|
/// @v.pipe(v.string(), v.uuid())
|
|
125
150
|
/// @a."string.uuid"
|
|
126
151
|
/// @e.Schema.UUID
|
|
152
|
+
/// @t.Type.String({ format: 'uuid' })
|
|
153
|
+
/// @j.{ type: 'string' as const, format: 'uuid' as const }
|
|
127
154
|
userId String
|
|
128
155
|
/// Prisma relation definition
|
|
129
156
|
user User @relation(fields: [userId], references: [id])
|
|
@@ -296,6 +323,108 @@ export const PostSchema = Schema.Struct({
|
|
|
296
323
|
export type Post = Schema.Schema.Type<typeof PostSchema>
|
|
297
324
|
```
|
|
298
325
|
|
|
326
|
+
### TypeBox
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
import { type Static, Type } from '@sinclair/typebox'
|
|
330
|
+
|
|
331
|
+
export const UserSchema = Type.Object({
|
|
332
|
+
/** Primary key */
|
|
333
|
+
id: Type.String({ format: 'uuid' }),
|
|
334
|
+
/** Display name */
|
|
335
|
+
name: Type.String({ minLength: 1, maxLength: 50 }),
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
export type User = Static<typeof UserSchema>
|
|
339
|
+
|
|
340
|
+
export const PostSchema = Type.Object({
|
|
341
|
+
/** Primary key */
|
|
342
|
+
id: Type.String({ format: 'uuid' }),
|
|
343
|
+
/** Article title */
|
|
344
|
+
title: Type.String({ minLength: 1, maxLength: 100 }),
|
|
345
|
+
/** Body content (no length limit) */
|
|
346
|
+
content: Type.String(),
|
|
347
|
+
/** Foreign key referencing User.id */
|
|
348
|
+
userId: Type.String({ format: 'uuid' }),
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
export type Post = Static<typeof PostSchema>
|
|
352
|
+
|
|
353
|
+
export const UserRelationsSchema = Type.Object({
|
|
354
|
+
...UserSchema.properties,
|
|
355
|
+
posts: Type.Array(PostSchema),
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
export type UserRelations = Static<typeof UserRelationsSchema>
|
|
359
|
+
|
|
360
|
+
export const PostRelationsSchema = Type.Object({
|
|
361
|
+
...PostSchema.properties,
|
|
362
|
+
user: UserSchema,
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
export type PostRelations = Static<typeof PostRelationsSchema>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### AJV (JSON Schema)
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
import type { FromSchema } from 'json-schema-to-ts'
|
|
372
|
+
|
|
373
|
+
export const UserSchema = {
|
|
374
|
+
type: 'object' as const,
|
|
375
|
+
properties: {
|
|
376
|
+
/** Primary key */
|
|
377
|
+
id: { type: 'string' as const, format: 'uuid' as const },
|
|
378
|
+
/** Display name */
|
|
379
|
+
name: { type: 'string' as const, minLength: 1, maxLength: 50 },
|
|
380
|
+
},
|
|
381
|
+
required: ['id', 'name'] as const,
|
|
382
|
+
additionalProperties: false,
|
|
383
|
+
} as const
|
|
384
|
+
|
|
385
|
+
export type User = FromSchema<typeof UserSchema>
|
|
386
|
+
|
|
387
|
+
export const PostSchema = {
|
|
388
|
+
type: 'object' as const,
|
|
389
|
+
properties: {
|
|
390
|
+
/** Primary key */
|
|
391
|
+
id: { type: 'string' as const, format: 'uuid' as const },
|
|
392
|
+
/** Article title */
|
|
393
|
+
title: { type: 'string' as const, minLength: 1, maxLength: 100 },
|
|
394
|
+
/** Body content (no length limit) */
|
|
395
|
+
content: { type: 'string' as const },
|
|
396
|
+
/** Foreign key referencing User.id */
|
|
397
|
+
userId: { type: 'string' as const, format: 'uuid' as const },
|
|
398
|
+
},
|
|
399
|
+
required: ['id', 'title', 'content', 'userId'] as const,
|
|
400
|
+
additionalProperties: false,
|
|
401
|
+
} as const
|
|
402
|
+
|
|
403
|
+
export type Post = FromSchema<typeof PostSchema>
|
|
404
|
+
|
|
405
|
+
export const UserRelationsSchema = {
|
|
406
|
+
type: 'object' as const,
|
|
407
|
+
properties: {
|
|
408
|
+
...UserSchema.properties,
|
|
409
|
+
posts: { type: 'array' as const, items: PostSchema },
|
|
410
|
+
},
|
|
411
|
+
additionalProperties: false,
|
|
412
|
+
} as const
|
|
413
|
+
|
|
414
|
+
export type UserRelations = FromSchema<typeof UserRelationsSchema>
|
|
415
|
+
|
|
416
|
+
export const PostRelationsSchema = {
|
|
417
|
+
type: 'object' as const,
|
|
418
|
+
properties: {
|
|
419
|
+
...PostSchema.properties,
|
|
420
|
+
user: UserSchema,
|
|
421
|
+
},
|
|
422
|
+
additionalProperties: false,
|
|
423
|
+
} as const
|
|
424
|
+
|
|
425
|
+
export type PostRelations = FromSchema<typeof PostRelationsSchema>
|
|
426
|
+
```
|
|
427
|
+
|
|
299
428
|
### Drizzle
|
|
300
429
|
|
|
301
430
|
```ts
|
|
@@ -344,19 +473,25 @@ erDiagram
|
|
|
344
473
|
|
|
345
474
|
### Ecto
|
|
346
475
|
|
|
476
|
+
Each model is output as a separate `.ex` file (1 model = 1 file), following Elixir conventions.
|
|
477
|
+
|
|
347
478
|
```elixir
|
|
348
479
|
defmodule DBSchema.User do
|
|
349
480
|
use Ecto.Schema
|
|
481
|
+
@moduledoc false
|
|
350
482
|
|
|
351
483
|
@primary_key {:id, :binary_id, autogenerate: true}
|
|
484
|
+
@foreign_key_type :binary_id
|
|
352
485
|
|
|
353
486
|
@type t :: %__MODULE__{
|
|
354
487
|
id: Ecto.UUID.t(),
|
|
355
|
-
name: String.t()
|
|
488
|
+
name: String.t(),
|
|
489
|
+
posts: [DBSchema.Post.t()]
|
|
356
490
|
}
|
|
357
491
|
|
|
358
492
|
schema "user" do
|
|
359
493
|
field(:name, :string)
|
|
494
|
+
has_many(:posts, DBSchema.Post, foreign_key: :user_id)
|
|
360
495
|
end
|
|
361
496
|
end
|
|
362
497
|
```
|
|
@@ -364,20 +499,23 @@ end
|
|
|
364
499
|
```elixir
|
|
365
500
|
defmodule DBSchema.Post do
|
|
366
501
|
use Ecto.Schema
|
|
502
|
+
@moduledoc false
|
|
367
503
|
|
|
368
504
|
@primary_key {:id, :binary_id, autogenerate: true}
|
|
505
|
+
@foreign_key_type :binary_id
|
|
369
506
|
|
|
370
507
|
@type t :: %__MODULE__{
|
|
371
508
|
id: Ecto.UUID.t(),
|
|
372
509
|
title: String.t(),
|
|
373
510
|
content: String.t(),
|
|
374
|
-
|
|
511
|
+
user: DBSchema.User.t() | nil
|
|
375
512
|
}
|
|
376
513
|
|
|
377
514
|
schema "post" do
|
|
378
515
|
field(:title, :string)
|
|
379
516
|
field(:content, :string)
|
|
380
|
-
field(:
|
|
517
|
+
field(:user_id, :binary_id, source: :userId)
|
|
518
|
+
belongs_to(:user, DBSchema.User, foreign_key: :user_id, define_field: false)
|
|
381
519
|
end
|
|
382
520
|
end
|
|
383
521
|
```
|
|
@@ -466,6 +604,24 @@ generator Hekireki-Effect {
|
|
|
466
604
|
relation = true // Generate relation schemas (default: false)
|
|
467
605
|
}
|
|
468
606
|
|
|
607
|
+
// TypeBox Generator
|
|
608
|
+
generator Hekireki-TypeBox {
|
|
609
|
+
provider = "hekireki-typebox"
|
|
610
|
+
output = "./typebox" // Output path (default: ./typebox/index.ts)
|
|
611
|
+
type = true // Generate TypeScript types (default: false)
|
|
612
|
+
comment = true // Include schema documentation (default: false)
|
|
613
|
+
relation = true // Generate relation schemas (default: false)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// AJV (JSON Schema) Generator
|
|
617
|
+
generator Hekireki-AJV {
|
|
618
|
+
provider = "hekireki-ajv"
|
|
619
|
+
output = "./ajv" // Output path (default: ./ajv/index.ts)
|
|
620
|
+
type = true // Generate TypeScript types (default: false)
|
|
621
|
+
comment = true // Include schema documentation (default: false)
|
|
622
|
+
relation = true // Generate relation schemas (default: false)
|
|
623
|
+
}
|
|
624
|
+
|
|
469
625
|
// Drizzle ORM Schema Generator
|
|
470
626
|
generator Hekireki-Drizzle {
|
|
471
627
|
provider = "hekireki-drizzle"
|
|
@@ -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
|
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as fmt } from "../../format-CzXgkLDe.js";
|
|
3
|
-
import {
|
|
3
|
+
import { d as mkdir, f as writeFile, o as makeSnakeCase } from "../../utils-COHZyQue.js";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import pkg from "@prisma/generator-helper";
|
|
6
6
|
|
|
7
7
|
//#region src/helper/drizzle.ts
|
|
8
|
-
function resolveProvider(provider) {
|
|
9
|
-
switch (provider) {
|
|
10
|
-
case "postgresql":
|
|
11
|
-
case "cockroachdb": return "postgresql";
|
|
12
|
-
case "mysql": return "mysql";
|
|
13
|
-
case "sqlite": return "sqlite";
|
|
14
|
-
default: throw new Error(`Unsupported provider: ${provider}`);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
8
|
const PG_SCALAR_MAP = {
|
|
18
9
|
String: "text()",
|
|
19
10
|
Int: "integer()",
|
|
@@ -140,13 +131,6 @@ function toCamelCase(name) {
|
|
|
140
131
|
function isFieldDefault(v) {
|
|
141
132
|
return typeof v === "object" && v !== null && "name" in v;
|
|
142
133
|
}
|
|
143
|
-
function insertColName(expr, colName) {
|
|
144
|
-
const parenIdx = expr.indexOf("(");
|
|
145
|
-
if (parenIdx === -1) return expr;
|
|
146
|
-
const fnName = expr.slice(0, parenIdx);
|
|
147
|
-
const rest = expr.slice(parenIdx + 1);
|
|
148
|
-
return rest === ")" ? `${fnName}('${colName}')` : `${fnName}('${colName}', ${rest}`;
|
|
149
|
-
}
|
|
150
134
|
function resolveScalarType(field, provider) {
|
|
151
135
|
if (field.nativeType && provider !== "sqlite") {
|
|
152
136
|
const [nativeName, nativeArgs] = field.nativeType;
|
|
@@ -179,7 +163,11 @@ function makeColumnExpr(field, provider, imports, enums) {
|
|
|
179
163
|
const baseExpr = resolveScalarType(field, provider);
|
|
180
164
|
const fnName = baseExpr.match(/^(\w+)/)?.[1];
|
|
181
165
|
if (fnName) imports.addCore(fnName);
|
|
182
|
-
|
|
166
|
+
const parenIdx = baseExpr.indexOf("(");
|
|
167
|
+
if (parenIdx === -1) return baseExpr;
|
|
168
|
+
const baseFnName = baseExpr.slice(0, parenIdx);
|
|
169
|
+
const rest = baseExpr.slice(parenIdx + 1);
|
|
170
|
+
return rest === ")" ? `${baseFnName}('${colName}')` : `${baseFnName}('${colName}', ${rest}`;
|
|
183
171
|
}
|
|
184
172
|
function makeDefaultChain(dflt, fieldType, provider, imports) {
|
|
185
173
|
if (dflt === void 0 || dflt === null) return "";
|
|
@@ -228,9 +216,6 @@ function makeColumn(field, model, provider, imports, enums) {
|
|
|
228
216
|
].join("");
|
|
229
217
|
return `${field.name}: ${colExpr}${chain}`;
|
|
230
218
|
}
|
|
231
|
-
function makeEnums() {
|
|
232
|
-
return [];
|
|
233
|
-
}
|
|
234
219
|
function makeCompositeConstraints(model, imports, indexes) {
|
|
235
220
|
const pkLine = model.primaryKey ? (() => {
|
|
236
221
|
imports.addCore("primaryKey");
|
|
@@ -291,9 +276,16 @@ function makeRelations(models, imports) {
|
|
|
291
276
|
});
|
|
292
277
|
}
|
|
293
278
|
function drizzleSchema(datamodel, provider, indexes) {
|
|
294
|
-
const db =
|
|
279
|
+
const db = (() => {
|
|
280
|
+
switch (provider) {
|
|
281
|
+
case "postgresql":
|
|
282
|
+
case "cockroachdb": return "postgresql";
|
|
283
|
+
case "mysql": return "mysql";
|
|
284
|
+
case "sqlite": return "sqlite";
|
|
285
|
+
default: throw new Error(`Unsupported provider: ${provider}`);
|
|
286
|
+
}
|
|
287
|
+
})();
|
|
295
288
|
const imports = new ImportTracker(db);
|
|
296
|
-
const enumLines = makeEnums();
|
|
297
289
|
const tableLines = datamodel.models.map((model) => makeTable(model, db, imports, datamodel.enums, indexes));
|
|
298
290
|
const relationsLines = makeRelations(datamodel.models, imports);
|
|
299
291
|
const tableLinesWithGap = tableLines.flatMap((line, i) => i < tableLines.length - 1 ? [line, ""] : [line]);
|
|
@@ -301,7 +293,6 @@ function drizzleSchema(datamodel, provider, indexes) {
|
|
|
301
293
|
return [
|
|
302
294
|
imports.generate(),
|
|
303
295
|
"",
|
|
304
|
-
...enumLines.length > 0 ? [...enumLines, ""] : [],
|
|
305
296
|
...tableLinesWithGap,
|
|
306
297
|
...relationsLinesWithGap.length > 0 ? ["", ...relationsLinesWithGap] : []
|
|
307
298
|
].join("\n");
|