hekireki 0.2.5 → 0.2.6
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.
|
@@ -2,71 +2,58 @@ 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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
};
|
|
19
22
|
}
|
|
20
|
-
/* ───────── Main generator ───────────────────── */
|
|
21
23
|
export function ectoSchemas(models, app) {
|
|
22
|
-
|
|
23
|
-
/** Timestamp column aliases (snake_case & camelCase) */
|
|
24
|
-
const insertedAliases = ['inserted_at', 'created_at', 'createdAt'];
|
|
25
|
-
const updatedAliases = ['updated_at', 'modified_at', 'updatedAt', 'modifiedAt'];
|
|
26
|
-
return mutableModels
|
|
24
|
+
return models
|
|
27
25
|
.map((model) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const isCompositePK = model.primaryKey && model.primaryKey.fields.length > 1;
|
|
31
|
-
if (!(idFields.length || isCompositePK))
|
|
26
|
+
const idField = model.fields.find((f) => f.isId);
|
|
27
|
+
if (!idField)
|
|
32
28
|
return '';
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
...(
|
|
29
|
+
const pk = getPrimaryKeyConfig(idField);
|
|
30
|
+
const fields = model.fields.filter((f) => !(f.relationName || (f.isId && pk.omitIdFieldInSchema)));
|
|
31
|
+
const typeSpecFields = [
|
|
32
|
+
`id: ${pk.typeSpec}`,
|
|
33
|
+
...fields.map((f) => `${f.name}: ${ectoTypeToTypespec(prismaTypeToEctoType(f.type))}`),
|
|
34
|
+
];
|
|
35
|
+
const typeSpecLines = [
|
|
36
|
+
' @type t :: %__MODULE__{',
|
|
37
|
+
...typeSpecFields.map((line, i) => {
|
|
38
|
+
const isLast = i === typeSpecFields.length - 1;
|
|
39
|
+
return ` ${line}${isLast ? '' : ','}`;
|
|
40
|
+
}),
|
|
41
|
+
' }',
|
|
42
42
|
];
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!(insertedField || updatedField))
|
|
48
|
-
return '';
|
|
49
|
-
const hasCustom = (insertedField && insertedField.name !== 'inserted_at') ||
|
|
50
|
-
(updatedField && updatedField.name !== 'updated_at');
|
|
51
|
-
if (!hasCustom)
|
|
52
|
-
return ' timestamps()'; // both defaults → short form
|
|
53
|
-
// Always include both keys when custom names are involved
|
|
54
|
-
const insertedName = insertedField ? insertedField.name : 'inserted_at';
|
|
55
|
-
const updatedName = updatedField ? updatedField.name : 'updated_at';
|
|
56
|
-
return ` timestamps(inserted_at: :${insertedName}, updated_at: :${updatedName})`;
|
|
57
|
-
})();
|
|
58
|
-
/* ── Assemble final module code ───────────── */
|
|
43
|
+
const schemaFields = fields.map((f) => {
|
|
44
|
+
const type = prismaTypeToEctoType(f.type);
|
|
45
|
+
return ` field(:${f.name}, :${type})`;
|
|
46
|
+
});
|
|
59
47
|
const lines = [
|
|
60
48
|
`defmodule ${app}.${model.name} do`,
|
|
61
49
|
' use Ecto.Schema',
|
|
62
|
-
'
|
|
50
|
+
'',
|
|
51
|
+
` ${pk.line}`,
|
|
52
|
+
'',
|
|
53
|
+
...typeSpecLines,
|
|
54
|
+
'',
|
|
63
55
|
` 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
|
-
}),
|
|
69
|
-
...(timestampsLine ? [timestampsLine] : []),
|
|
56
|
+
...schemaFields,
|
|
70
57
|
' end',
|
|
71
58
|
'end',
|
|
72
59
|
];
|
|
@@ -75,11 +62,29 @@ export function ectoSchemas(models, app) {
|
|
|
75
62
|
.filter(Boolean)
|
|
76
63
|
.join('\n\n');
|
|
77
64
|
}
|
|
78
|
-
|
|
65
|
+
function ectoTypeToTypespec(type) {
|
|
66
|
+
switch (type) {
|
|
67
|
+
case 'string':
|
|
68
|
+
return 'String.t()';
|
|
69
|
+
case 'integer':
|
|
70
|
+
return 'integer()';
|
|
71
|
+
case 'float':
|
|
72
|
+
return 'float()';
|
|
73
|
+
case 'boolean':
|
|
74
|
+
return 'boolean()';
|
|
75
|
+
case 'binary_id':
|
|
76
|
+
return 'Ecto.UUID.t()';
|
|
77
|
+
case 'naive_datetime':
|
|
78
|
+
return 'NaiveDateTime.t()';
|
|
79
|
+
case 'utc_datetime':
|
|
80
|
+
return 'DateTime.t()';
|
|
81
|
+
default:
|
|
82
|
+
return 'term()';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
79
85
|
export async function writeEctoSchemasToFiles(models, app, outDir) {
|
|
80
|
-
const mutableModels = makeMutable(models);
|
|
81
86
|
await fsp.mkdir(outDir, { recursive: true });
|
|
82
|
-
for (const model of
|
|
87
|
+
for (const model of models) {
|
|
83
88
|
const code = ectoSchemas([model], app);
|
|
84
89
|
if (!code.trim())
|
|
85
90
|
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.6",
|
|
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": [
|