hekireki 0.2.5 → 0.2.7
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 +18 -4
- package/dist/generator/ecto/generator/ecto.js +90 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,9 +191,15 @@ erDiagram
|
|
|
191
191
|
```elixir
|
|
192
192
|
defmodule DBSchema.User do
|
|
193
193
|
use Ecto.Schema
|
|
194
|
-
|
|
194
|
+
|
|
195
|
+
@primary_key {:id, :binary_id, autogenerate: true}
|
|
196
|
+
|
|
197
|
+
@type t :: %__MODULE__{
|
|
198
|
+
id: Ecto.UUID.t(),
|
|
199
|
+
name: String.t()
|
|
200
|
+
}
|
|
201
|
+
|
|
195
202
|
schema "user" do
|
|
196
|
-
field(:id, :binary_id, primary_key: true)
|
|
197
203
|
field(:name, :string)
|
|
198
204
|
end
|
|
199
205
|
end
|
|
@@ -202,9 +208,17 @@ end
|
|
|
202
208
|
```elixir
|
|
203
209
|
defmodule DBSchema.Post do
|
|
204
210
|
use Ecto.Schema
|
|
205
|
-
|
|
211
|
+
|
|
212
|
+
@primary_key {:id, :binary_id, autogenerate: true}
|
|
213
|
+
|
|
214
|
+
@type t :: %__MODULE__{
|
|
215
|
+
id: Ecto.UUID.t(),
|
|
216
|
+
title: String.t(),
|
|
217
|
+
content: String.t(),
|
|
218
|
+
userId: String.t()
|
|
219
|
+
}
|
|
220
|
+
|
|
206
221
|
schema "post" do
|
|
207
|
-
field(:id, :binary_id, primary_key: true)
|
|
208
222
|
field(:title, :string)
|
|
209
223
|
field(:content, :string)
|
|
210
224
|
field(:userId, :string)
|
|
@@ -2,70 +2,107 @@ import fsp from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { snakeCase } from '../../../shared/utils/index.js';
|
|
4
4
|
import { prismaTypeToEctoType } from '../utils/prisma-type-to-ecto-type.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
function getPrimaryKeyConfig(field) {
|
|
6
|
+
if (field.type === 'String' &&
|
|
7
|
+
field.default &&
|
|
8
|
+
typeof field.default === 'object' &&
|
|
9
|
+
'name' in field.default &&
|
|
10
|
+
field.default.name === 'uuid') {
|
|
11
|
+
return {
|
|
12
|
+
line: '@primary_key {:id, :binary_id, autogenerate: true}',
|
|
13
|
+
typeSpec: 'Ecto.UUID.t()',
|
|
14
|
+
omitIdFieldInSchema: true,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
line: '@primary_key false',
|
|
19
|
+
typeSpec: 'String.t()',
|
|
20
|
+
omitIdFieldInSchema: false,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function getFieldDefaultOption(field) {
|
|
8
24
|
const def = field.default;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
25
|
+
if (def === undefined || def === null)
|
|
26
|
+
return null;
|
|
27
|
+
if (typeof def === 'string')
|
|
28
|
+
return `default: "${def}"`;
|
|
29
|
+
if (typeof def === 'number' || typeof def === 'boolean')
|
|
30
|
+
return `default: ${def}`;
|
|
31
|
+
return null;
|
|
12
32
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
33
|
+
function ectoTypeToTypespec(type) {
|
|
34
|
+
switch (type) {
|
|
35
|
+
case 'string': return 'String.t()';
|
|
36
|
+
case 'integer': return 'integer()';
|
|
37
|
+
case 'float': return 'float()';
|
|
38
|
+
case 'boolean': return 'boolean()';
|
|
39
|
+
case 'binary_id': return 'Ecto.UUID.t()';
|
|
40
|
+
case 'naive_datetime': return 'NaiveDateTime.t()';
|
|
41
|
+
case 'utc_datetime': return 'DateTime.t()';
|
|
42
|
+
default: return 'term()';
|
|
43
|
+
}
|
|
19
44
|
}
|
|
20
|
-
|
|
21
|
-
export function ectoSchemas(models, app) {
|
|
22
|
-
const mutableModels = makeMutable(models);
|
|
23
|
-
/** Timestamp column aliases (snake_case & camelCase) */
|
|
45
|
+
function buildTimestampsLine(fields) {
|
|
24
46
|
const insertedAliases = ['inserted_at', 'created_at', 'createdAt'];
|
|
25
47
|
const updatedAliases = ['updated_at', 'modified_at', 'updatedAt', 'modifiedAt'];
|
|
26
|
-
|
|
48
|
+
const inserted = fields.find((f) => insertedAliases.includes(f.name));
|
|
49
|
+
const updated = fields.find((f) => updatedAliases.includes(f.name));
|
|
50
|
+
const exclude = new Set();
|
|
51
|
+
if (inserted)
|
|
52
|
+
exclude.add(inserted.name);
|
|
53
|
+
if (updated)
|
|
54
|
+
exclude.add(updated.name);
|
|
55
|
+
if (!(inserted || updated))
|
|
56
|
+
return { line: null, exclude };
|
|
57
|
+
if (inserted?.name === 'inserted_at' && updated?.name === 'updated_at') {
|
|
58
|
+
return { line: ' timestamps()', exclude };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
line: ` timestamps(inserted_at: :${inserted?.name ?? 'inserted_at'}, updated_at: :${updated?.name ?? 'updated_at'})`,
|
|
62
|
+
exclude,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function ectoSchemas(models, app) {
|
|
66
|
+
return models
|
|
27
67
|
.map((model) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const isCompositePK = model.primaryKey && model.primaryKey.fields.length > 1;
|
|
31
|
-
if (!(idFields.length || isCompositePK))
|
|
68
|
+
const idField = model.fields.find((f) => f.isId);
|
|
69
|
+
if (!idField)
|
|
32
70
|
return '';
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
...(
|
|
71
|
+
const pk = getPrimaryKeyConfig(idField);
|
|
72
|
+
const fields = model.fields.map((f) => ({ ...f }));
|
|
73
|
+
const { line: timestampsLine, exclude: timestampsExclude } = buildTimestampsLine(fields);
|
|
74
|
+
const schemaFieldsRaw = fields.filter((f) => !(f.relationName ||
|
|
75
|
+
(f.isId && pk.omitIdFieldInSchema) ||
|
|
76
|
+
timestampsExclude.has(f.name)));
|
|
77
|
+
const typeSpecFields = [
|
|
78
|
+
`id: ${pk.typeSpec}`,
|
|
79
|
+
...schemaFieldsRaw.map((f) => `${f.name}: ${ectoTypeToTypespec(prismaTypeToEctoType(f.type))}`),
|
|
42
80
|
];
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
return `
|
|
57
|
-
})
|
|
58
|
-
/* ── Assemble final module code ───────────── */
|
|
81
|
+
const typeSpecLines = [
|
|
82
|
+
' @type t :: %__MODULE__{',
|
|
83
|
+
...typeSpecFields.map((line, i) => {
|
|
84
|
+
const isLast = i === typeSpecFields.length - 1;
|
|
85
|
+
return ` ${line}${isLast ? '' : ','}`;
|
|
86
|
+
}),
|
|
87
|
+
' }',
|
|
88
|
+
];
|
|
89
|
+
const schemaFields = schemaFieldsRaw.map((f) => {
|
|
90
|
+
const type = f.isId ? 'binary_id' : prismaTypeToEctoType(f.type);
|
|
91
|
+
const primary = f.isId && !pk.omitIdFieldInSchema ? ', primary_key: true' : '';
|
|
92
|
+
const defaultOpt = getFieldDefaultOption(f);
|
|
93
|
+
const defaultClause = defaultOpt ? `, ${defaultOpt}` : '';
|
|
94
|
+
return ` field(:${f.name}, :${type}${primary}${defaultClause})`;
|
|
95
|
+
});
|
|
59
96
|
const lines = [
|
|
60
97
|
`defmodule ${app}.${model.name} do`,
|
|
61
98
|
' use Ecto.Schema',
|
|
62
|
-
'
|
|
99
|
+
'',
|
|
100
|
+
` ${pk.line}`,
|
|
101
|
+
'',
|
|
102
|
+
...typeSpecLines,
|
|
103
|
+
'',
|
|
63
104
|
` schema "${snakeCase(model.name)}" do`,
|
|
64
|
-
...
|
|
65
|
-
const type = f.isId ? pkType : prismaTypeToEctoType(f.type);
|
|
66
|
-
const primary = f.isId && !isCompositePK ? ', primary_key: true' : '';
|
|
67
|
-
return ` field(:${f.name}, :${type}${primary})`;
|
|
68
|
-
}),
|
|
105
|
+
...schemaFields,
|
|
69
106
|
...(timestampsLine ? [timestampsLine] : []),
|
|
70
107
|
' end',
|
|
71
108
|
'end',
|
|
@@ -75,11 +112,9 @@ export function ectoSchemas(models, app) {
|
|
|
75
112
|
.filter(Boolean)
|
|
76
113
|
.join('\n\n');
|
|
77
114
|
}
|
|
78
|
-
/* ───────── File writer ───────────────────────── */
|
|
79
115
|
export async function writeEctoSchemasToFiles(models, app, outDir) {
|
|
80
|
-
const mutableModels = makeMutable(models);
|
|
81
116
|
await fsp.mkdir(outDir, { recursive: true });
|
|
82
|
-
for (const model of
|
|
117
|
+
for (const model of models) {
|
|
83
118
|
const code = ectoSchemas([model], app);
|
|
84
119
|
if (!code.trim())
|
|
85
120
|
continue;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hekireki",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.7",
|
|
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": [
|