hekireki 0.7.0 → 0.7.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 +282 -14
- 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 +34 -9
- package/dist/generator/effect/index.js +17 -3
- package/dist/generator/gorm/index.d.ts +6 -0
- package/dist/generator/gorm/index.js +370 -0
- package/dist/generator/mermaid-er/index.js +6 -12
- package/dist/generator/sea-orm/index.d.ts +6 -0
- package/dist/generator/sea-orm/index.js +444 -0
- package/dist/generator/sqlalchemy/index.d.ts +6 -0
- package/dist/generator/sqlalchemy/index.js +458 -0
- 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-Cc0YxSiO.js → prisma-ChsFqlYX.js} +6 -6
- package/dist/utils-COHZyQue.js +116 -0
- package/package.json +18 -7
- package/dist/utils-DeZn2r_T.js +0 -287
|
@@ -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");
|
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
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 =
|
|
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 = [];
|
|
@@ -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/effect.ts
|
|
9
|
+
function makeEffectInfer(modelName) {
|
|
10
|
+
return `export type ${modelName} = Schema.Schema.Type<typeof ${modelName}Schema>`;
|
|
11
|
+
}
|
|
12
|
+
function makeEffectSchema(modelName, fields) {
|
|
13
|
+
return `export const ${modelName}Schema = Schema.Struct({\n${fields}\n})`;
|
|
14
|
+
}
|
|
15
|
+
function makeEffectProperties(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 ?? "Schema.Unknown"},`;
|
|
18
|
+
}).join("\n");
|
|
19
|
+
}
|
|
20
|
+
function makeEffectEnumExpression(values) {
|
|
21
|
+
return `Schema.Literal(${values.map((v) => `'${v}'`).join(", ")})`;
|
|
22
|
+
}
|
|
9
23
|
const PRISMA_TO_EFFECT = {
|
|
10
24
|
String: "Schema.String",
|
|
11
25
|
Int: "Schema.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([effect(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), makeEffectRelations) : ""].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);
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { d as mkdir, f as writeFile, n as getString, o as makeSnakeCase } from "../../utils-COHZyQue.js";
|
|
3
|
+
import path, { dirname } from "node:path";
|
|
4
|
+
import pkg from "@prisma/generator-helper";
|
|
5
|
+
|
|
6
|
+
//#region src/helper/gorm.ts
|
|
7
|
+
const PRISMA_TO_GO = {
|
|
8
|
+
String: "string",
|
|
9
|
+
Int: "int",
|
|
10
|
+
BigInt: "int64",
|
|
11
|
+
Float: "float64",
|
|
12
|
+
Decimal: "float64",
|
|
13
|
+
Boolean: "bool",
|
|
14
|
+
DateTime: "time.Time",
|
|
15
|
+
Json: "datatypes.JSON",
|
|
16
|
+
Bytes: "[]byte"
|
|
17
|
+
};
|
|
18
|
+
function prismaTypeToGoType(type, isRequired) {
|
|
19
|
+
const base = PRISMA_TO_GO[type] ?? "string";
|
|
20
|
+
if (!isRequired && base !== "[]byte" && base !== "datatypes.JSON") return `*${base}`;
|
|
21
|
+
return base;
|
|
22
|
+
}
|
|
23
|
+
function resolveNativeType(field) {
|
|
24
|
+
if (!field.nativeType) return null;
|
|
25
|
+
const [nativeName, nativeArgs] = field.nativeType;
|
|
26
|
+
const args = nativeArgs ?? [];
|
|
27
|
+
switch (nativeName) {
|
|
28
|
+
case "VarChar":
|
|
29
|
+
case "Char": return args.length > 0 ? `varchar(${args[0]})` : null;
|
|
30
|
+
case "Text":
|
|
31
|
+
case "MediumText":
|
|
32
|
+
case "LongText":
|
|
33
|
+
case "TinyText": return "text";
|
|
34
|
+
case "SmallInt":
|
|
35
|
+
case "TinyInt": return "smallint";
|
|
36
|
+
case "MediumInt": return "mediumint";
|
|
37
|
+
case "DoublePrecision":
|
|
38
|
+
case "Double":
|
|
39
|
+
case "Real": return "double precision";
|
|
40
|
+
case "Decimal":
|
|
41
|
+
case "Money": return args.length >= 2 ? `decimal(${args[0]},${args[1]})` : "decimal";
|
|
42
|
+
case "Uuid": return "char(36)";
|
|
43
|
+
case "Timestamp":
|
|
44
|
+
case "Timestamptz": return "timestamp";
|
|
45
|
+
case "Date": return "date";
|
|
46
|
+
case "Time":
|
|
47
|
+
case "Timetz": return "time";
|
|
48
|
+
case "JsonB": return "jsonb";
|
|
49
|
+
case "Xml": return "xml";
|
|
50
|
+
default: return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function getAssociations(model, allModels) {
|
|
54
|
+
const belongsTo = [];
|
|
55
|
+
const hasMany = [];
|
|
56
|
+
const hasOne = [];
|
|
57
|
+
const manyToMany = [];
|
|
58
|
+
for (const field of model.fields) {
|
|
59
|
+
if (field.kind !== "object") continue;
|
|
60
|
+
if (field.relationFromFields && field.relationFromFields.length > 0) belongsTo.push({
|
|
61
|
+
name: field.name,
|
|
62
|
+
targetModel: field.type,
|
|
63
|
+
foreignKey: field.relationFromFields[0],
|
|
64
|
+
references: field.relationToFields?.[0] ?? "id"
|
|
65
|
+
});
|
|
66
|
+
else if (field.isList) {
|
|
67
|
+
const targetModel = allModels.find((m) => m.name === field.type);
|
|
68
|
+
if (!targetModel) continue;
|
|
69
|
+
if (targetModel.fields.find((f) => f.relationName === field.relationName && f.kind === "object")?.isList) manyToMany.push({
|
|
70
|
+
name: field.name,
|
|
71
|
+
targetModel: field.type,
|
|
72
|
+
relationName: field.relationName ?? `${model.name}To${field.type}`
|
|
73
|
+
});
|
|
74
|
+
else {
|
|
75
|
+
const fkField = targetModel.fields.find((f) => f.relationName === field.relationName && f.relationFromFields && f.relationFromFields.length > 0);
|
|
76
|
+
const foreignKey = fkField?.relationFromFields?.[0];
|
|
77
|
+
if (!foreignKey) continue;
|
|
78
|
+
const references = fkField?.relationToFields?.[0] ?? "id";
|
|
79
|
+
hasMany.push({
|
|
80
|
+
name: field.name,
|
|
81
|
+
targetModel: field.type,
|
|
82
|
+
foreignKey,
|
|
83
|
+
references,
|
|
84
|
+
isList: true
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
const targetModel = allModels.find((m) => m.name === field.type);
|
|
89
|
+
if (!targetModel) continue;
|
|
90
|
+
const fkField = targetModel.fields.find((f) => f.relationName === field.relationName && f.relationFromFields && f.relationFromFields.length > 0);
|
|
91
|
+
const foreignKey = fkField?.relationFromFields?.[0];
|
|
92
|
+
if (!foreignKey) continue;
|
|
93
|
+
const references = fkField?.relationToFields?.[0] ?? "id";
|
|
94
|
+
hasOne.push({
|
|
95
|
+
name: field.name,
|
|
96
|
+
targetModel: field.type,
|
|
97
|
+
foreignKey,
|
|
98
|
+
references,
|
|
99
|
+
isList: false
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
belongsTo,
|
|
105
|
+
hasMany,
|
|
106
|
+
hasOne,
|
|
107
|
+
manyToMany
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function isFunctionDefault(def) {
|
|
111
|
+
return def !== null && typeof def === "object" && "name" in def;
|
|
112
|
+
}
|
|
113
|
+
function isAutoincrement(field) {
|
|
114
|
+
return isFunctionDefault(field.default) && field.default.name === "autoincrement";
|
|
115
|
+
}
|
|
116
|
+
function formatGoDefault(def) {
|
|
117
|
+
if (def === void 0 || def === null) return null;
|
|
118
|
+
if (typeof def === "boolean") return def ? "true" : "false";
|
|
119
|
+
if (typeof def === "number") return String(def);
|
|
120
|
+
if (typeof def === "string") return def;
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
function buildGormTags(field, isPk, isCompositePk, compositeIndexTags) {
|
|
124
|
+
const columnName = field.dbName ?? makeSnakeCase(field.name);
|
|
125
|
+
const parts = [`column:${columnName}`];
|
|
126
|
+
if (isPk) {
|
|
127
|
+
parts.push("primaryKey");
|
|
128
|
+
if (isAutoincrement(field)) parts.push("autoIncrement");
|
|
129
|
+
if (isFunctionDefault(field.default) && field.default.name === "uuid") parts.push("type:char(36)");
|
|
130
|
+
}
|
|
131
|
+
if (field.isUnique) parts.push("uniqueIndex");
|
|
132
|
+
for (const tag of compositeIndexTags) parts.push(tag);
|
|
133
|
+
const nativeType = resolveNativeType(field);
|
|
134
|
+
if (nativeType && !isPk) parts.push(`type:${nativeType}`);
|
|
135
|
+
else if (nativeType && isPk && !isFunctionDefault(field.default)) parts.push(`type:${nativeType}`);
|
|
136
|
+
else if (nativeType && isPk && isFunctionDefault(field.default) && field.default.name !== "uuid") parts.push(`type:${nativeType}`);
|
|
137
|
+
if (!isPk || isCompositePk) if (field.type === "DateTime" && isFunctionDefault(field.default) && field.default.name === "now") parts.push("autoCreateTime");
|
|
138
|
+
else if (field.isUpdatedAt) {} else {
|
|
139
|
+
const defaultVal = formatGoDefault(field.default);
|
|
140
|
+
if (defaultVal !== null) parts.push(`default:${defaultVal}`);
|
|
141
|
+
}
|
|
142
|
+
else if (isPk && !isCompositePk) {
|
|
143
|
+
if (field.type === "DateTime" && isFunctionDefault(field.default) && field.default.name === "now") parts.push("autoCreateTime");
|
|
144
|
+
}
|
|
145
|
+
if (field.isUpdatedAt) parts.push("autoUpdateTime");
|
|
146
|
+
if (field.isRequired && !isPk) parts.push("not null");
|
|
147
|
+
return `\`gorm:"${parts.join(";")}" json:"${columnName}"\``;
|
|
148
|
+
}
|
|
149
|
+
function collectCompositeIndexTags(model, indexes) {
|
|
150
|
+
const tagMap = /* @__PURE__ */ new Map();
|
|
151
|
+
const addTag = (fieldName, tag) => {
|
|
152
|
+
const existing = tagMap.get(fieldName) ?? [];
|
|
153
|
+
existing.push(tag);
|
|
154
|
+
tagMap.set(fieldName, existing);
|
|
155
|
+
};
|
|
156
|
+
for (const fields of model.uniqueFields) {
|
|
157
|
+
if (fields.length <= 1) continue;
|
|
158
|
+
const idxName = `idx_${fields.map((f) => {
|
|
159
|
+
return model.fields.find((mf) => mf.name === f)?.dbName ?? makeSnakeCase(f);
|
|
160
|
+
}).join("_")}_unique`;
|
|
161
|
+
for (const f of fields) addTag(f, `uniqueIndex:${idxName}`);
|
|
162
|
+
}
|
|
163
|
+
for (const idx of indexes) {
|
|
164
|
+
if (idx.model !== model.name) continue;
|
|
165
|
+
if (idx.type !== "normal" && idx.type !== "fulltext") continue;
|
|
166
|
+
const idxName = idx.dbName ?? idx.name ?? `idx_${idx.fields.map((f) => makeSnakeCase(f.name)).join("_")}`;
|
|
167
|
+
for (const f of idx.fields) addTag(f.name, `index:${idxName}`);
|
|
168
|
+
}
|
|
169
|
+
return tagMap;
|
|
170
|
+
}
|
|
171
|
+
const GO_INITIALISMS = new Set([
|
|
172
|
+
"acl",
|
|
173
|
+
"api",
|
|
174
|
+
"ascii",
|
|
175
|
+
"cpu",
|
|
176
|
+
"css",
|
|
177
|
+
"dns",
|
|
178
|
+
"eof",
|
|
179
|
+
"guid",
|
|
180
|
+
"html",
|
|
181
|
+
"http",
|
|
182
|
+
"https",
|
|
183
|
+
"id",
|
|
184
|
+
"ip",
|
|
185
|
+
"json",
|
|
186
|
+
"lhs",
|
|
187
|
+
"qps",
|
|
188
|
+
"ram",
|
|
189
|
+
"rhs",
|
|
190
|
+
"rpc",
|
|
191
|
+
"sla",
|
|
192
|
+
"smtp",
|
|
193
|
+
"sql",
|
|
194
|
+
"ssh",
|
|
195
|
+
"tcp",
|
|
196
|
+
"tls",
|
|
197
|
+
"ttl",
|
|
198
|
+
"udp",
|
|
199
|
+
"ui",
|
|
200
|
+
"uid",
|
|
201
|
+
"uri",
|
|
202
|
+
"url",
|
|
203
|
+
"utf8",
|
|
204
|
+
"uuid",
|
|
205
|
+
"vm",
|
|
206
|
+
"xml",
|
|
207
|
+
"xmpp",
|
|
208
|
+
"xsrf",
|
|
209
|
+
"xss"
|
|
210
|
+
]);
|
|
211
|
+
/**
|
|
212
|
+
* Split a camelCase/PascalCase name into words, applying Go initialism rules.
|
|
213
|
+
* e.g. "userId" -> ["User", "ID"], "avatarUrl" -> ["Avatar", "URL"],
|
|
214
|
+
* "ipAddress" -> ["IP", "Address"], "createdAt" -> ["Created", "At"]
|
|
215
|
+
*/
|
|
216
|
+
function splitGoWords(name) {
|
|
217
|
+
const parts = name.replace(/([a-z0-9])([A-Z])/g, "$1\0$2").split("\0");
|
|
218
|
+
const result = [];
|
|
219
|
+
let i = 0;
|
|
220
|
+
while (i < parts.length) {
|
|
221
|
+
const lower = parts[i].toLowerCase();
|
|
222
|
+
if (GO_INITIALISMS.has(lower)) {
|
|
223
|
+
result.push(lower.toUpperCase());
|
|
224
|
+
i++;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
result.push(parts[i].charAt(0).toUpperCase() + parts[i].slice(1));
|
|
228
|
+
i++;
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
function goFieldName(name) {
|
|
233
|
+
return splitGoWords(name).join("");
|
|
234
|
+
}
|
|
235
|
+
function generateStructField(field, isPk, isCompositePk, compositeIndexTags, _enumNames) {
|
|
236
|
+
const fieldName = goFieldName(field.name);
|
|
237
|
+
let goType;
|
|
238
|
+
if (field.kind === "enum") goType = field.isRequired ? "string" : "*string";
|
|
239
|
+
else goType = prismaTypeToGoType(field.type, field.isRequired);
|
|
240
|
+
const tag = buildGormTags(field, isPk, isCompositePk, compositeIndexTags);
|
|
241
|
+
const tagStr = tag ? ` ${tag}` : "";
|
|
242
|
+
return `\t${fieldName} ${goType}${tagStr}`;
|
|
243
|
+
}
|
|
244
|
+
function needsReferencesTag(references) {
|
|
245
|
+
return references !== "id";
|
|
246
|
+
}
|
|
247
|
+
function buildRelationTag(parts) {
|
|
248
|
+
return `\`gorm:"${parts.join(";")}"\``;
|
|
249
|
+
}
|
|
250
|
+
function generateRelationFields(model, associations) {
|
|
251
|
+
const lines = [];
|
|
252
|
+
for (const assoc of associations.belongsTo) {
|
|
253
|
+
const fieldName = goFieldName(assoc.name);
|
|
254
|
+
const fkFieldName = goFieldName(assoc.foreignKey);
|
|
255
|
+
const refsFieldName = goFieldName(assoc.references);
|
|
256
|
+
const isAmbiguous = fieldName !== assoc.targetModel || associations.belongsTo.filter((a) => a.targetModel === assoc.targetModel).length > 1;
|
|
257
|
+
const tagParts = [];
|
|
258
|
+
if (isAmbiguous) tagParts.push(`foreignKey:${fkFieldName}`);
|
|
259
|
+
if (needsReferencesTag(assoc.references)) tagParts.push(`references:${refsFieldName}`);
|
|
260
|
+
if (tagParts.length > 0) lines.push(`\t${fieldName} ${assoc.targetModel} ${buildRelationTag(tagParts)}`);
|
|
261
|
+
else lines.push(`\t${fieldName} ${assoc.targetModel}`);
|
|
262
|
+
}
|
|
263
|
+
for (const assoc of associations.hasMany) {
|
|
264
|
+
const tagParts = [`foreignKey:${goFieldName(assoc.foreignKey)}`];
|
|
265
|
+
if (needsReferencesTag(assoc.references)) tagParts.push(`references:${goFieldName(assoc.references)}`);
|
|
266
|
+
lines.push(`\t${goFieldName(assoc.name)} []${assoc.targetModel} ${buildRelationTag(tagParts)}`);
|
|
267
|
+
}
|
|
268
|
+
for (const assoc of associations.hasOne) {
|
|
269
|
+
const tagParts = [`foreignKey:${goFieldName(assoc.foreignKey)}`];
|
|
270
|
+
if (needsReferencesTag(assoc.references)) tagParts.push(`references:${goFieldName(assoc.references)}`);
|
|
271
|
+
lines.push(`\t${goFieldName(assoc.name)} ${assoc.targetModel} ${buildRelationTag(tagParts)}`);
|
|
272
|
+
}
|
|
273
|
+
for (const assoc of associations.manyToMany) {
|
|
274
|
+
const [leftName, rightName] = model.name < assoc.targetModel ? [model.name, assoc.targetModel] : [assoc.targetModel, model.name];
|
|
275
|
+
const joinTable = `_${leftName}To${rightName}`;
|
|
276
|
+
lines.push(`\t${goFieldName(assoc.name)} []${assoc.targetModel} \`gorm:"many2many:${joinTable};"\``);
|
|
277
|
+
}
|
|
278
|
+
return lines;
|
|
279
|
+
}
|
|
280
|
+
function generateModelStruct(model, allModels, enums, indexes) {
|
|
281
|
+
const idField = model.fields.find((f) => f.isId);
|
|
282
|
+
const compositePkFieldNames = new Set(model.primaryKey?.fields ?? []);
|
|
283
|
+
const isCompositePk = !idField && compositePkFieldNames.size > 0;
|
|
284
|
+
if (!(idField || isCompositePk)) return null;
|
|
285
|
+
const associations = getAssociations(model, allModels);
|
|
286
|
+
const enumNames = new Set((enums ?? []).map((e) => e.name));
|
|
287
|
+
const compositeTagMap = collectCompositeIndexTags(model, indexes);
|
|
288
|
+
const tableName = model.dbName ?? makeSnakeCase(model.name);
|
|
289
|
+
const fieldLines = model.fields.filter((f) => f.kind !== "object").map((field) => {
|
|
290
|
+
return generateStructField(field, field.isId || compositePkFieldNames.has(field.name), isCompositePk, compositeTagMap.get(field.name) ?? [], enumNames);
|
|
291
|
+
});
|
|
292
|
+
const relationLines = generateRelationFields(model, associations);
|
|
293
|
+
const lines = [
|
|
294
|
+
`type ${model.name} struct {`,
|
|
295
|
+
...fieldLines,
|
|
296
|
+
...relationLines.length > 0 ? relationLines : [],
|
|
297
|
+
"}"
|
|
298
|
+
];
|
|
299
|
+
if (tableName !== makeSnakeCase(model.name)) {
|
|
300
|
+
lines.push("");
|
|
301
|
+
lines.push(`func (${model.name}) TableName() string {`);
|
|
302
|
+
lines.push(`\treturn "${tableName}"`);
|
|
303
|
+
lines.push("}");
|
|
304
|
+
}
|
|
305
|
+
return lines.join("\n");
|
|
306
|
+
}
|
|
307
|
+
function collectImports(models) {
|
|
308
|
+
const imports = [];
|
|
309
|
+
const needsTime = models.some((m) => m.fields.some((f) => f.kind !== "object" && f.type === "DateTime"));
|
|
310
|
+
const needsDatatypes = models.some((m) => m.fields.some((f) => f.kind !== "object" && f.type === "Json"));
|
|
311
|
+
if (needsTime) imports.push("\"time\"");
|
|
312
|
+
if (needsDatatypes) imports.push("\"gorm.io/datatypes\"");
|
|
313
|
+
return imports;
|
|
314
|
+
}
|
|
315
|
+
function generateGormModels(models, enums, indexes, packageName = "model") {
|
|
316
|
+
const idx = indexes ?? [];
|
|
317
|
+
const modelBodies = models.map((model) => generateModelStruct(model, models, enums, idx)).filter((body) => body !== null);
|
|
318
|
+
const imports = collectImports(models);
|
|
319
|
+
const lines = [`package ${packageName}`];
|
|
320
|
+
if (imports.length > 0) {
|
|
321
|
+
lines.push("");
|
|
322
|
+
if (imports.length === 1) lines.push(`import ${imports[0]}`);
|
|
323
|
+
else {
|
|
324
|
+
lines.push("import (");
|
|
325
|
+
for (const imp of imports) lines.push(`\t${imp}`);
|
|
326
|
+
lines.push(")");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
lines.push("");
|
|
330
|
+
lines.push(modelBodies.join("\n\n"));
|
|
331
|
+
lines.push("");
|
|
332
|
+
return lines.join("\n");
|
|
333
|
+
}
|
|
334
|
+
async function writeGormFile(models, outPath, enums, indexes, packageName = "model") {
|
|
335
|
+
const mkdirResult = await mkdir(dirname(outPath));
|
|
336
|
+
if (!mkdirResult.ok) return mkdirResult;
|
|
337
|
+
const writeResult = await writeFile(outPath, generateGormModels(models, enums, indexes, packageName));
|
|
338
|
+
if (!writeResult.ok) return writeResult;
|
|
339
|
+
console.log(`wrote ${outPath}`);
|
|
340
|
+
return {
|
|
341
|
+
ok: true,
|
|
342
|
+
value: void 0
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/generator/gorm/index.ts
|
|
348
|
+
const { generatorHandler } = pkg;
|
|
349
|
+
async function main(options) {
|
|
350
|
+
if (!(options.generator.isCustomOutput && options.generator.output?.value)) throw new Error("output is required for Hekireki-GORM. Please specify output in your generator config.");
|
|
351
|
+
const output = options.generator.output.value;
|
|
352
|
+
const resolved = path.extname(output) ? output : path.join(output, "models.go");
|
|
353
|
+
const packageName = getString(options.generator.config.package, "model");
|
|
354
|
+
const enums = options.dmmf.datamodel.enums;
|
|
355
|
+
const indexes = options.dmmf.datamodel.indexes;
|
|
356
|
+
const result = await writeGormFile(options.dmmf.datamodel.models, resolved, enums, indexes, packageName);
|
|
357
|
+
if (!result.ok) throw new Error(`Failed to write GORM models: ${result.error}`);
|
|
358
|
+
}
|
|
359
|
+
generatorHandler({
|
|
360
|
+
onManifest() {
|
|
361
|
+
return {
|
|
362
|
+
defaultOutput: ".",
|
|
363
|
+
prettyName: "Hekireki-GORM"
|
|
364
|
+
};
|
|
365
|
+
},
|
|
366
|
+
onGenerate: main
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
//#endregion
|
|
370
|
+
export { main };
|
|
@@ -1,32 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { d as mkdir, f as writeFile, u as stripAnnotations } from "../../utils-COHZyQue.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import pkg from "@prisma/generator-helper";
|
|
5
5
|
|
|
6
6
|
//#region src/helper/mermaid-er.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
function removeDuplicateRelations(relations) {
|
|
8
|
+
return [...new Set(relations)];
|
|
9
|
+
}
|
|
10
10
|
const RELATIONSHIPS = {
|
|
11
11
|
"zero-one": "|o",
|
|
12
12
|
one: "||",
|
|
13
13
|
"zero-many": "}o",
|
|
14
14
|
many: "}|"
|
|
15
15
|
};
|
|
16
|
-
function toMermaidType(prismaType) {
|
|
17
|
-
return prismaType.toLowerCase();
|
|
18
|
-
}
|
|
19
16
|
function modelFields(model) {
|
|
20
17
|
const fkFields = new Set(model.fields.filter((f) => f.relationFromFields && f.relationFromFields.length > 0).flatMap((f) => f.relationFromFields ?? []));
|
|
21
18
|
return model.fields.map((field) => {
|
|
22
19
|
if (field.relationName) return null;
|
|
23
|
-
const commentPart = field.documentation
|
|
24
|
-
const trimmed = line.trim();
|
|
25
|
-
return !(trimmed.startsWith(ZOD_ANNOTATION) || trimmed.startsWith(VALIBOT_ANNOTATION) || trimmed.startsWith("@a.") || trimmed.startsWith("@e.") || trimmed.includes(RELATION_ANNOTATION) || trimmed === "@z" || trimmed === "@v" || trimmed === "@a" || trimmed === "@e");
|
|
26
|
-
}).join("\n").trim() : "";
|
|
20
|
+
const commentPart = stripAnnotations(field.documentation) ?? "";
|
|
27
21
|
const keyMarker = field.isId ? "PK" : fkFields.has(field.name) ? "FK" : "";
|
|
28
22
|
const keyPart = keyMarker ? ` ${keyMarker}` : "";
|
|
29
|
-
return ` ${
|
|
23
|
+
return ` ${field.type.toLowerCase()} ${field.name}${keyPart}${commentPart ? ` "${commentPart}"` : ""}`;
|
|
30
24
|
}).filter((field) => field !== null);
|
|
31
25
|
}
|
|
32
26
|
function modelInfo(model) {
|