hekireki 0.2.1 โ†’ 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  - ๐Ÿ’Ž Automatically generates [Zod](https://zod.dev/) schemas from your Prisma schema
8
8
  - ๐Ÿค– Automatically generates [Valibot](https://valibot.dev/) schemas from your Prisma schema
9
9
  - ๐Ÿ“Š Creates [Mermaid](https://mermaid.js.org/) ER diagrams
10
+ - ๐Ÿงช Generates [Ecto](https://hexdocs.pm/ecto/Ecto.Schema.html) schemas for Elixir projects
11
+ โš ๏ธ Foreign key constraints are **not** included โ€” manage relationships in your application logic
10
12
 
11
13
  ## Installation
12
14
 
@@ -35,13 +37,19 @@ generator Hekireki-ER {
35
37
  generator Hekireki-Zod {
36
38
  provider = "hekireki-zod"
37
39
  type = true
38
- comment = false
40
+ comment = true
39
41
  }
40
42
 
41
43
  generator Hekireki-Valibot {
42
44
  provider = "hekireki-valibot"
43
45
  type = true
44
- comment = false
46
+ comment = true
47
+ }
48
+
49
+ generator Hekireki-Ecto {
50
+ provider = "hekireki-ecto"
51
+ output = "schema"
52
+ app = "DBSchema"
45
53
  }
46
54
 
47
55
  model User {
@@ -63,12 +71,10 @@ model Post {
63
71
  /// @z.uuid()
64
72
  /// @v.pipe(v.string(), v.uuid())
65
73
  id String @id @default(uuid())
66
-
67
74
  /// Article title
68
75
  /// @z.string().min(1).max(100)
69
76
  /// @v.pipe(v.string(), v.minLength(1), v.maxLength(100))
70
77
  title String
71
-
72
78
  /// Body content (no length limit)
73
79
  /// @z.string()
74
80
  /// @v.string()
@@ -180,6 +186,32 @@ erDiagram
180
186
  }
181
187
  ```
182
188
 
189
+ ## Ecto
190
+
191
+ ```elixir
192
+ defmodule DBSchema.User do
193
+ use Ecto.Schema
194
+ @primary_key false
195
+ schema "user" do
196
+ field(:id, :binary_id, primary_key: true)
197
+ field(:name, :string)
198
+ end
199
+ end
200
+ ```
201
+
202
+ ```elixir
203
+ defmodule DBSchema.Post do
204
+ use Ecto.Schema
205
+ @primary_key false
206
+ schema "post" do
207
+ field(:id, :binary_id, primary_key: true)
208
+ field(:title, :string)
209
+ field(:content, :string)
210
+ field(:userId, :string)
211
+ end
212
+ end
213
+ ```
214
+
183
215
  ## Configuration
184
216
 
185
217
  ### Zod Generator Options
@@ -208,6 +240,13 @@ erDiagram
208
240
  | `output` | `string` | `./mermaid-er` | Output directory |
209
241
  | `file` | `string` | `ER.md` | File Name |
210
242
 
243
+ ### Ecto Generator Options
244
+
245
+ | Option | Type | Default | Description |
246
+ |--------------|-----------|-------------------------------------|--------------------------------------------------|
247
+ | `output` | `string` | `./ecto` | Output directory |
248
+ | `app` | `string` | `MyApp` | App Name |
249
+
211
250
  โš ๏ธ WARNING: Potential Breaking Changes Without Notice
212
251
 
213
252
  This project is in **early development** and being maintained by a developer with about 2 years of experience. While I'm doing my best to create a useful tool:
@@ -0,0 +1,3 @@
1
+ import type { DMMF } from '@prisma/generator-helper';
2
+ export declare function ectoSchemas(models: readonly DMMF.Model[], app: string | string[]): string;
3
+ export declare function writeEctoSchemasToFiles(models: readonly DMMF.Model[], app: string | string[], outDir: string): Promise<void>;
@@ -0,0 +1,61 @@
1
+ import fsp from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { snakeCase } from '../../../shared/utils/index.js';
4
+ import { prismaTypeToEctoType } from '../utils/prisma-type-to-ecto-type.js';
5
+ function getPrimaryKeyType(field) {
6
+ const def = field.default;
7
+ return def && typeof def === 'object' && 'name' in def && def.name === 'uuid' ? 'binary_id' : 'id';
8
+ }
9
+ function makeMutable(models) {
10
+ return models.map((m) => ({
11
+ ...m,
12
+ fields: m.fields?.map((f) => ({ ...f })) ?? [],
13
+ }));
14
+ }
15
+ export function ectoSchemas(models, app) {
16
+ const mutableModels = makeMutable(models);
17
+ return mutableModels
18
+ .map((model) => {
19
+ const idFields = model.fields.filter((f) => f.isId);
20
+ const isCompositePK = model.primaryKey && model.primaryKey.fields.length > 1;
21
+ if (!(idFields.length || isCompositePK)) {
22
+ return '';
23
+ }
24
+ const pkField = idFields[0];
25
+ const pkType = pkField ? getPrimaryKeyType(pkField) : ':id';
26
+ const excludedFieldNames = ['inserted_at', 'updated_at'];
27
+ const fields = model.fields.filter((f) => !(f.relationName || excludedFieldNames.includes(f.name)));
28
+ const hasInsertedAt = model.fields.some((f) => f.name === 'inserted_at');
29
+ const hasUpdatedAt = model.fields.some((f) => f.name === 'updated_at');
30
+ const lines = [
31
+ `defmodule ${app}.${model.name} do`,
32
+ ' use Ecto.Schema',
33
+ ' @primary_key false',
34
+ ` schema "${snakeCase(model.name)}" do`,
35
+ ...fields.map((f) => {
36
+ const type = f.isId ? pkType : prismaTypeToEctoType(f.type);
37
+ const primary = f.isId && !isCompositePK ? ', primary_key: true' : '';
38
+ return ` field(:${f.name}, :${type}${primary})`;
39
+ }),
40
+ ...(hasInsertedAt ? [' field :inserted_at, :utc_datetime'] : []),
41
+ ...(hasUpdatedAt ? [' field :updated_at, :utc_datetime'] : []),
42
+ ' end',
43
+ 'end',
44
+ ];
45
+ return lines.join('\n');
46
+ })
47
+ .filter(Boolean)
48
+ .join('\n\n');
49
+ }
50
+ export async function writeEctoSchemasToFiles(models, app, outDir) {
51
+ const mutableModels = makeMutable(models);
52
+ await fsp.mkdir(outDir, { recursive: true });
53
+ for (const model of mutableModels) {
54
+ const code = ectoSchemas([model], app);
55
+ if (!code.trim())
56
+ continue;
57
+ const filePath = join(outDir, `${snakeCase(model.name)}.ex`);
58
+ await fsp.writeFile(filePath, code, 'utf8');
59
+ console.log(`โœ… wrote ${filePath}`);
60
+ }
61
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import type { GeneratorOptions } from '@prisma/generator-helper';
3
+ export declare function main(options: GeneratorOptions): Promise<void>;
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import pkg from '@prisma/generator-helper';
3
+ import { writeEctoSchemasToFiles } from './generator/ecto.js';
4
+ const { generatorHandler } = pkg;
5
+ export async function main(options) {
6
+ const output = options.generator.output?.value ?? './ecto';
7
+ const app = options.generator.config?.app ?? 'MyApp';
8
+ await writeEctoSchemasToFiles(options.dmmf.datamodel.models, app, output);
9
+ }
10
+ generatorHandler({
11
+ onManifest() {
12
+ return {
13
+ defaultOutput: './ecto/',
14
+ prettyName: 'Hekireki-Ecto',
15
+ };
16
+ },
17
+ onGenerate: main,
18
+ });
@@ -0,0 +1 @@
1
+ export { prismaTypeToEctoType } from './prisma-type-to-ecto-type.js';
@@ -0,0 +1 @@
1
+ export { prismaTypeToEctoType } from './prisma-type-to-ecto-type.js';
@@ -0,0 +1 @@
1
+ export declare function prismaTypeToEctoType(type: string): 'integer' | 'string' | 'boolean' | 'utc_datetime';
@@ -0,0 +1,11 @@
1
+ export function prismaTypeToEctoType(type) {
2
+ if (type === 'Int')
3
+ return 'integer';
4
+ if (type === 'String')
5
+ return 'string';
6
+ if (type === 'Boolean')
7
+ return 'boolean';
8
+ if (type === 'DateTime')
9
+ return 'utc_datetime';
10
+ return 'string';
11
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Decapitalize the first letter of a string
3
+ * @param { string } str - String to decapitalize
4
+ * @returns { string } String with the first letter in lowercase
5
+ *
6
+ * @example
7
+ * decapitalize('Posts') // Returns: 'posts'
8
+ * decapitalize('User') // Returns: 'user'
9
+ * decapitalize('Api') // Returns: 'api'
10
+ *
11
+ * @remarks
12
+ * - Leaves the rest of the string unchanged
13
+ * - Returns an empty string if the input is empty
14
+ */
15
+ export declare function decapitalize(str: string): string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Decapitalize the first letter of a string
3
+ * @param { string } str - String to decapitalize
4
+ * @returns { string } String with the first letter in lowercase
5
+ *
6
+ * @example
7
+ * decapitalize('Posts') // Returns: 'posts'
8
+ * decapitalize('User') // Returns: 'user'
9
+ * decapitalize('Api') // Returns: 'api'
10
+ *
11
+ * @remarks
12
+ * - Leaves the rest of the string unchanged
13
+ * - Returns an empty string if the input is empty
14
+ */
15
+ export function decapitalize(str) {
16
+ return `${str.charAt(0).toLowerCase()}${str.slice(1)}`;
17
+ }
@@ -1 +1,3 @@
1
1
  export { capitalize } from './capitalize.js';
2
+ export { decapitalize } from './decapitalize.js';
3
+ export { snakeCase } from './snake-case.js';
@@ -1 +1,3 @@
1
1
  export { capitalize } from './capitalize.js';
2
+ export { decapitalize } from './decapitalize.js';
3
+ export { snakeCase } from './snake-case.js';
@@ -0,0 +1 @@
1
+ export declare function snakeCase(name: string): string;
@@ -0,0 +1,3 @@
1
+ export function snakeCase(name) {
2
+ return `${name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase()}`;
3
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hekireki",
3
3
  "type": "module",
4
- "version": "0.2.1",
4
+ "version": "0.2.2",
5
5
  "license": "MIT",
6
6
  "description": "Hekireki is a tool that generates validation schemas for Zod and Valibot, as well as ER diagrams, from Prisma schemas annotated with comments.",
7
7
  "keywords": [
@@ -29,7 +29,8 @@
29
29
  "bin": {
30
30
  "hekireki-zod": "dist/generator/zod/index.js",
31
31
  "hekireki-valibot": "dist/generator/valibot/index.js",
32
- "hekireki-mermaid-er": "dist/generator/mermaid-er/index.js"
32
+ "hekireki-mermaid-er": "dist/generator/mermaid-er/index.js",
33
+ "hekireki-ecto": "dist/generator/ecto/index.js"
33
34
  },
34
35
  "scripts": {
35
36
  "generate": "prisma generate",