hekireki 0.7.0 → 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 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 Effect Schema, as well as [Drizzle ORM](https://orm.drizzle.team/) schemas and ER diagrams, from [Prisma](https://www.prisma.io/) schemas annotated with comments.
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,6 +10,8 @@
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`)
@@ -62,6 +64,20 @@ generator Hekireki-Effect {
62
64
  relation = true
63
65
  }
64
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
+
65
81
  generator Hekireki-Drizzle {
66
82
  provider = "hekireki-drizzle"
67
83
  }
@@ -88,12 +104,16 @@ model User {
88
104
  /// @v.pipe(v.string(), v.uuid())
89
105
  /// @a."string.uuid"
90
106
  /// @e.Schema.UUID
107
+ /// @t.Type.String({ format: 'uuid' })
108
+ /// @j.{ type: 'string' as const, format: 'uuid' as const }
91
109
  id String @id @default(uuid())
92
110
  /// Display name
93
111
  /// @z.string().min(1).max(50)
94
112
  /// @v.pipe(v.string(), v.minLength(1), v.maxLength(50))
95
113
  /// @a."1 <= string <= 50"
96
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 }
97
117
  name String
98
118
  /// One-to-many relation to Post
99
119
  posts Post[]
@@ -105,24 +125,32 @@ model Post {
105
125
  /// @v.pipe(v.string(), v.uuid())
106
126
  /// @a."string.uuid"
107
127
  /// @e.Schema.UUID
128
+ /// @t.Type.String({ format: 'uuid' })
129
+ /// @j.{ type: 'string' as const, format: 'uuid' as const }
108
130
  id String @id @default(uuid())
109
131
  /// Article title
110
132
  /// @z.string().min(1).max(100)
111
133
  /// @v.pipe(v.string(), v.minLength(1), v.maxLength(100))
112
134
  /// @a."1 <= string <= 100"
113
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 }
114
138
  title String
115
139
  /// Body content (no length limit)
116
140
  /// @z.string()
117
141
  /// @v.string()
118
142
  /// @a."string"
119
143
  /// @e.Schema.String
144
+ /// @t.Type.String()
145
+ /// @j.{ type: 'string' as const }
120
146
  content String
121
147
  /// Foreign key referencing User.id
122
148
  /// @z.uuid()
123
149
  /// @v.pipe(v.string(), v.uuid())
124
150
  /// @a."string.uuid"
125
151
  /// @e.Schema.UUID
152
+ /// @t.Type.String({ format: 'uuid' })
153
+ /// @j.{ type: 'string' as const, format: 'uuid' as const }
126
154
  userId String
127
155
  /// Prisma relation definition
128
156
  user User @relation(fields: [userId], references: [id])
@@ -295,6 +323,108 @@ export const PostSchema = Schema.Struct({
295
323
  export type Post = Schema.Schema.Type<typeof PostSchema>
296
324
  ```
297
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
+
298
428
  ### Drizzle
299
429
 
300
430
  ```ts
@@ -390,19 +520,6 @@ defmodule DBSchema.Post do
390
520
  end
391
521
  ```
392
522
 
393
- **Supported features:**
394
-
395
- | Feature | Details |
396
- |---|---|
397
- | Primary keys | UUID (`@default(uuid())`), autoincrement (`@default(autoincrement())`), CUID, composite (`@@id`) |
398
- | Associations | `belongs_to`, `has_many`, `has_one` with correct FK types |
399
- | Timestamps | `timestamps()` with `inserted_at_source`/`updated_at_source` for `@map`/camelCase fields |
400
- | Enums | `Ecto.Enum` with `values: [...]` |
401
- | Array fields | `{:array, :type}` for Prisma list scalars (`String[]`, `Int[]`, etc.) |
402
- | Name mapping | `@@map` → schema table name, `@map` → field `source:` option |
403
- | Typespecs | `@type t :: %__MODULE__{...}` auto-generated for Dialyzer |
404
- | `@moduledoc` | Uses `///` model documentation, or `false` when absent |
405
-
406
523
  ### DBML
407
524
 
408
525
  ```dbml
@@ -487,6 +604,24 @@ generator Hekireki-Effect {
487
604
  relation = true // Generate relation schemas (default: false)
488
605
  }
489
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
+
490
625
  // Drizzle ORM Schema Generator
491
626
  generator Hekireki-Drizzle {
492
627
  provider = "hekireki-drizzle"
@@ -0,0 +1,6 @@
1
+ import { GeneratorOptions } from "@prisma/generator-helper";
2
+
3
+ //#region src/generator/ajv/index.d.ts
4
+ declare function main(options: GeneratorOptions): Promise<void>;
5
+ //#endregion
6
+ export { main };
@@ -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 { N as mkdir, O as parseDocumentWithoutAnnotations, P as writeFile, a as getBool, d as makeArktypeProperties, f as makeArktypeSchema, j as schemaFromFields, l as makeArktypeEnumExpression, u as makeArktypeInfer, w as makeValidationExtractor } from "../../utils-DeZn2r_T.js";
4
- import { n as validationSchemas, t as makeRelationsOnly } from "../../prisma-Cc0YxSiO.js";
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 = options.generator.config?.relation === "true" || Array.isArray(options.generator.config?.relation) && options.generator.config?.relation[0] === "true";
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 { F as writeFileBinary, M as stripAnnotations, N as mkdir, P as writeFile, _ as makeEnum, i as formatConstraints, o as getString, r as escapeNote, t as combineKeys, y as makeRefName } from "../../utils-DeZn2r_T.js";
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: resolveFieldType(field, models, mapToDbSchema),
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: resolveDefaultValue(field),
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 = makeTableIndexes(model);
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 = getRelationOperator(models, relationFrom, relationTo);
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
- const makeDbmlFile = async (outputDir, content, fileName) => {
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
- const makePng = async (outputDir, dbml, fileName) => {
138
+ }
139
+ async function makePng(outputDir, dbml, fileName) {
130
140
  return makePngFile(`${outputDir}/${fileName}`, dbml);
131
- };
132
- const makePngFile = async (outputPath, dbml) => {
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 { N as mkdir, P as writeFile, b as makeSnakeCase } from "../../utils-DeZn2r_T.js";
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
- return insertColName(baseExpr, colName);
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 = resolveProvider(provider);
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");
@@ -1,9 +1,36 @@
1
1
  #!/usr/bin/env node
2
- import { N as mkdir, P as writeFile, b as makeSnakeCase, k as prismaTypeToEctoType, n as ectoTypeToTypespec } from "../../utils-DeZn2r_T.js";
2
+ import { d as mkdir, f as writeFile, o as makeSnakeCase } from "../../utils-COHZyQue.js";
3
3
  import { join } from "node:path";
4
4
  import pkg from "@prisma/generator-helper";
5
5
 
6
6
  //#region src/helper/ecto.ts
7
+ function prismaTypeToEctoType(type) {
8
+ if (type === "Int") return "integer";
9
+ if (type === "BigInt") return "integer";
10
+ if (type === "Float") return "float";
11
+ if (type === "Decimal") return "decimal";
12
+ if (type === "String") return "string";
13
+ if (type === "Boolean") return "boolean";
14
+ if (type === "DateTime") return "utc_datetime";
15
+ if (type === "Json") return "map";
16
+ if (type === "Bytes") return "binary";
17
+ return "string";
18
+ }
19
+ function ectoTypeToTypespec(type) {
20
+ switch (type) {
21
+ case "string": return "String.t()";
22
+ case "integer": return "integer()";
23
+ case "float": return "float()";
24
+ case "boolean": return "boolean()";
25
+ case "binary_id": return "Ecto.UUID.t()";
26
+ case "naive_datetime": return "NaiveDateTime.t()";
27
+ case "utc_datetime": return "DateTime.t()";
28
+ case "decimal": return "Decimal.t()";
29
+ case "map": return "map()";
30
+ case "binary": return "binary()";
31
+ default: return "term()";
32
+ }
33
+ }
7
34
  function getPrimaryKeyConfig(field) {
8
35
  const def = field.default;
9
36
  const isFunctionDefault = def && typeof def === "object" && "name" in def;
@@ -26,13 +53,6 @@ function getPrimaryKeyConfig(field) {
26
53
  useBinaryForeignKey: false
27
54
  };
28
55
  }
29
- function getFieldDefaultOption(field) {
30
- const def = field.default;
31
- if (def === void 0 || def === null) return null;
32
- if (typeof def === "string") return `default: "${def}"`;
33
- if (typeof def === "number" || typeof def === "boolean") return `default: ${def}`;
34
- return null;
35
- }
36
56
  function makeTimestampsLine(fields) {
37
57
  const insertedAliases = [
38
58
  "inserted_at",
@@ -183,7 +203,12 @@ function ectoSchemas(models, app, allModels, enums) {
183
203
  }
184
204
  const type = prismaTypeToEctoType(f.type);
185
205
  const ectoType = f.isList ? `{:array, :${type}}` : `:${type}`;
186
- const defaultOpt = getFieldDefaultOption(f);
206
+ const defaultOpt = ((def) => {
207
+ if (def === void 0 || def === null) return null;
208
+ if (typeof def === "string") return `default: "${def}"`;
209
+ if (typeof def === "number" || typeof def === "boolean") return `default: ${def}`;
210
+ return null;
211
+ })(f.default);
187
212
  return ` field(:${snakeName}, ${ectoType}${primary}${defaultOpt ? `, ${defaultOpt}` : ""}${sourceOpt})`;
188
213
  });
189
214
  const fkFieldLines = [];