hekireki 0.8.6 → 0.9.0

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/dist/bin/ajv.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/ajv.ts
4
4
  registerGenerator("ajv");
5
5
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/arktype.ts
4
4
  registerGenerator("arktype");
5
5
  //#endregion
package/dist/bin/dbml.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/dbml.ts
4
4
  registerGenerator("dbml");
5
5
  //#endregion
package/dist/bin/docs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/docs.ts
4
4
  registerGenerator("docs");
5
5
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/drizzle.ts
4
4
  registerGenerator("drizzle");
5
5
  //#endregion
package/dist/bin/ecto.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/ecto.ts
4
4
  registerGenerator("ecto");
5
5
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/effect.ts
4
4
  registerGenerator("effect");
5
5
  //#endregion
package/dist/bin/gorm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/gorm.ts
4
4
  registerGenerator("gorm");
5
5
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/mermaid-er.ts
4
4
  registerGenerator("mermaid-er");
5
5
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/sea-orm.ts
4
4
  registerGenerator("sea-orm");
5
5
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/sqlalchemy.ts
4
4
  registerGenerator("sqlalchemy");
5
5
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/typebox.ts
4
4
  registerGenerator("typebox");
5
5
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/valibot.ts
4
4
  registerGenerator("valibot");
5
5
  //#endregion
package/dist/bin/zod.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-DLQOqGdU.js";
2
+ import { t as registerGenerator } from "../bin-Dh-MFTeu.js";
3
3
  //#region src/bin/zod.ts
4
4
  registerGenerator("zod");
5
5
  //#endregion
@@ -1043,13 +1043,15 @@ const getOutputTypes = (dmmfOutputTypes) => dmmfOutputTypes.map((outputType) =>
1043
1043
  }))
1044
1044
  }));
1045
1045
  const getTypesData = (d) => {
1046
+ const prismaInputTypes = d.schema.inputObjectTypes.prisma ?? [];
1046
1047
  return {
1047
- inputTypes: getInputTypes(d.schema.inputObjectTypes.prisma ?? []),
1048
+ inputTypes: getInputTypes(prismaInputTypes),
1048
1049
  outputTypes: getOutputTypes([...d.schema.outputObjectTypes.model, ...d.schema.outputObjectTypes.prisma.filter((op) => op.name !== "Query" && op.name !== "Mutation")])
1049
1050
  };
1050
1051
  };
1051
1052
  const createTypes = (d) => {
1052
- return /* @__PURE__ */ jsx(TypesSection, { data: getTypesData(d) });
1053
+ const data = getTypesData(d);
1054
+ return /* @__PURE__ */ jsx(TypesSection, { data });
1053
1055
  };
1054
1056
  //#endregion
1055
1057
  //#region src/helper/docs/generator/model.tsx
@@ -1414,7 +1416,8 @@ const getModels$1 = (dmmf) => dmmf.datamodel.models.map((model) => ({
1414
1416
  }));
1415
1417
  const getModelData = (d) => ({ models: getModels$1(d) });
1416
1418
  const createModels = (d) => {
1417
- return /* @__PURE__ */ jsx(ModelsSection, { data: getModelData(d) });
1419
+ const data = getModelData(d);
1420
+ return /* @__PURE__ */ jsx(ModelsSection, { data });
1418
1421
  };
1419
1422
  //#endregion
1420
1423
  //#region src/helper/docs/generator/toc.tsx
@@ -1543,7 +1546,8 @@ const getTOCData = (d) => ({
1543
1546
  types: getTypes(d.schema)
1544
1547
  });
1545
1548
  const createTOC = (d) => {
1546
- return /* @__PURE__ */ jsx(TOCComponent, { data: getTOCData(d) });
1549
+ const data = getTOCData(d);
1550
+ return /* @__PURE__ */ jsx(TOCComponent, { data });
1547
1551
  };
1548
1552
  //#endregion
1549
1553
  //#region src/helper/docs/printer/index.tsx
@@ -1802,6 +1806,18 @@ function resolveVarNameByType(type, models) {
1802
1806
  function isFieldDefault(v) {
1803
1807
  return typeof v === "object" && v !== null && "name" in v;
1804
1808
  }
1809
+ function enumIdentifier(enumName) {
1810
+ return `${snakeToCamel(makeSnakeCase(enumName))}Enum`;
1811
+ }
1812
+ function makeEnumDeclarations(models, enums, provider, imports) {
1813
+ if (provider !== "postgresql") return [];
1814
+ const usedEnumNames = new Set(models.flatMap((m) => m.fields.filter((f) => f.kind === "enum").map((f) => f.type)));
1815
+ return enums.filter((e) => usedEnumNames.has(e.name)).map((e) => {
1816
+ imports.core.add("pgEnum");
1817
+ const values = e.values.map((v) => `'${v.name}'`).join(", ");
1818
+ return `export const ${enumIdentifier(e.name)} = pgEnum('${e.dbName ?? e.name}', [${values}])`;
1819
+ });
1820
+ }
1805
1821
  function resolveScalarType(field, provider) {
1806
1822
  if (field.nativeType && provider !== "sqlite") {
1807
1823
  const [nativeName, nativeArgs] = field.nativeType;
@@ -1816,10 +1832,7 @@ function makeColumnExpr(field, provider, imports, enums) {
1816
1832
  if (field.kind === "enum") {
1817
1833
  const enumDef = enums.find((e) => e.name === field.type);
1818
1834
  const enumValues = enumDef ? enumDef.values.map((v) => `'${v.name}'`).join(", ") : "";
1819
- if (provider === "postgresql") {
1820
- imports.core.add("pgEnum");
1821
- return `pgEnum('${enumDef?.dbName ?? field.type}', [${enumValues}])('${colName}')`;
1822
- }
1835
+ if (provider === "postgresql") return `${enumIdentifier(field.type)}('${colName}')`;
1823
1836
  if (provider === "mysql") {
1824
1837
  imports.core.add("mysqlEnum");
1825
1838
  return `mysqlEnum('${colName}', [${enumValues}])`;
@@ -1977,6 +1990,7 @@ function makeColumn(field, model, models, provider, imports, enums) {
1977
1990
  const hasCompositePK = model.primaryKey !== null;
1978
1991
  const colExpr = makeColumnExpr(field, provider, imports, enums);
1979
1992
  const chain = [
1993
+ field.isList && field.kind === "scalar" && provider === "postgresql" ? ".array()" : "",
1980
1994
  field.isId && !hasCompositePK ? isAutoincrement && provider === "sqlite" ? ".primaryKey({ autoIncrement: true })" : ".primaryKey()" : "",
1981
1995
  field.isRequired && !field.isId && !(isAutoincrement && provider === "postgresql") ? ".notNull()" : "",
1982
1996
  field.isUnique ? ".unique()" : "",
@@ -1986,8 +2000,7 @@ function makeColumn(field, model, models, provider, imports, enums) {
1986
2000
  if (r.needsSql) imports.orm.add("sql");
1987
2001
  return r.chain;
1988
2002
  })() : makeDefaultChain(field.default, field.type, provider, imports),
1989
- field.isUpdatedAt ? ".$onUpdate(() => new Date())" : "",
1990
- field.isList && field.kind === "scalar" && provider === "postgresql" ? ".array()" : ""
2003
+ field.isUpdatedAt ? ".$onUpdate(() => new Date())" : ""
1991
2004
  ].join("");
1992
2005
  return `${field.name}: ${colExpr}${chain}`;
1993
2006
  }
@@ -2055,6 +2068,7 @@ function makeRelations(models, imports) {
2055
2068
  function drizzleSchema(datamodel, provider, indexes) {
2056
2069
  const db = resolveDbProvider(provider);
2057
2070
  const imports = createImports();
2071
+ const enumLines = makeEnumDeclarations(datamodel.models, datamodel.enums, db, imports);
2058
2072
  const tableLines = datamodel.models.map((model) => makeTable(model, datamodel.models, db, imports, datamodel.enums, indexes));
2059
2073
  const relationsLines = makeRelations(datamodel.models, imports);
2060
2074
  const tableLinesWithGap = tableLines.flatMap((line, i) => i < tableLines.length - 1 ? [line, ""] : [line]);
@@ -2062,6 +2076,7 @@ function drizzleSchema(datamodel, provider, indexes) {
2062
2076
  return [
2063
2077
  generateImports(imports, db),
2064
2078
  "",
2079
+ ...enumLines.length > 0 ? [...enumLines, ""] : [],
2065
2080
  ...tableLinesWithGap,
2066
2081
  ...relationsLinesWithGap.length > 0 ? ["", ...relationsLinesWithGap] : []
2067
2082
  ].join("\n");
@@ -2198,6 +2213,7 @@ function getAssociations$3(model, allModels) {
2198
2213
  const belongsTo = [];
2199
2214
  const hasMany = [];
2200
2215
  const hasOne = [];
2216
+ const manyToMany = [];
2201
2217
  for (const field of model.fields) {
2202
2218
  if (field.kind !== "object") continue;
2203
2219
  if (field.relationFromFields && field.relationFromFields.length > 0) {
@@ -2213,7 +2229,15 @@ function getAssociations$3(model, allModels) {
2213
2229
  const targetModel = allModels.find((m) => m.name === field.type);
2214
2230
  if (!targetModel) continue;
2215
2231
  if (field.isList) {
2216
- if (targetModel.fields.find((f) => f.relationName === field.relationName && f.kind === "object")?.isList) continue;
2232
+ if (targetModel.fields.find((f) => f.relationName === field.relationName && f.kind === "object")?.isList) {
2233
+ const [left, right] = model.name < field.type ? [model.name, field.type] : [field.type, model.name];
2234
+ manyToMany.push({
2235
+ name: field.name,
2236
+ targetModel: field.type,
2237
+ joinThrough: `_${left}To${right}`
2238
+ });
2239
+ continue;
2240
+ }
2217
2241
  }
2218
2242
  const foreignKey = targetModel.fields.find((f) => f.relationName === field.relationName && f.relationFromFields && f.relationFromFields.length > 0)?.relationFromFields?.[0];
2219
2243
  if (!foreignKey) continue;
@@ -2231,7 +2255,8 @@ function getAssociations$3(model, allModels) {
2231
2255
  return {
2232
2256
  belongsTo,
2233
2257
  hasMany,
2234
- hasOne
2258
+ hasOne,
2259
+ manyToMany
2235
2260
  };
2236
2261
  }
2237
2262
  function ectoSchemas(models, app, allModels, enums) {
@@ -2271,7 +2296,8 @@ function ectoSchemas(models, app, allModels, enums) {
2271
2296
  }),
2272
2297
  ...associations.belongsTo.map((a) => `${makeSnakeCase(a.name)}: ${appName}.${a.targetModel}.t() | nil`),
2273
2298
  ...associations.hasOne.map((a) => `${makeSnakeCase(a.name)}: ${appName}.${a.targetModel}.t() | nil`),
2274
- ...associations.hasMany.map((a) => `${makeSnakeCase(a.name)}: [${appName}.${a.targetModel}.t()]`)
2299
+ ...associations.hasMany.map((a) => `${makeSnakeCase(a.name)}: [${appName}.${a.targetModel}.t()]`),
2300
+ ...associations.manyToMany.map((a) => `${makeSnakeCase(a.name)}: [${appName}.${a.targetModel}.t()]`)
2275
2301
  ];
2276
2302
  const typeSpecLines = [
2277
2303
  " @type t :: %__MODULE__{",
@@ -2294,7 +2320,8 @@ function ectoSchemas(models, app, allModels, enums) {
2294
2320
  const defaultOpt = ((def) => {
2295
2321
  if (def === void 0 || def === null) return null;
2296
2322
  if (typeof def === "string") return `default: "${def}"`;
2297
- if (typeof def === "number" || typeof def === "boolean") return `default: ${def}`;
2323
+ if (typeof def === "number") return type === "float" && Number.isInteger(def) ? `default: ${def}.0` : `default: ${def}`;
2324
+ if (typeof def === "boolean") return `default: ${def}`;
2298
2325
  return null;
2299
2326
  })(f.default);
2300
2327
  return ` field(:${snakeName}, ${ectoType}${primary}${defaultOpt ? `, ${defaultOpt}` : ""}${sourceOpt})`;
@@ -2331,6 +2358,9 @@ function ectoSchemas(models, app, allModels, enums) {
2331
2358
  const snakeFk = makeSnakeCase(a.foreignKey);
2332
2359
  return ` has_many(:${makeSnakeCase(a.name)}, ${appName}.${a.targetModel}, foreign_key: :${snakeFk})`;
2333
2360
  });
2361
+ const manyToManyLines = associations.manyToMany.map((a) => {
2362
+ return ` many_to_many(:${makeSnakeCase(a.name)}, ${appName}.${a.targetModel}, join_through: "${a.joinThrough}")`;
2363
+ });
2334
2364
  return [
2335
2365
  `defmodule ${appName}.${model.name} do`,
2336
2366
  " use Ecto.Schema",
@@ -2351,6 +2381,7 @@ function ectoSchemas(models, app, allModels, enums) {
2351
2381
  ...belongsToLines,
2352
2382
  ...hasOneLines,
2353
2383
  ...hasManyLines,
2384
+ ...manyToManyLines,
2354
2385
  ...timestampsLine ? [timestampsLine] : [],
2355
2386
  " end",
2356
2387
  "end"
@@ -2561,7 +2592,7 @@ function formatGoDefault(def) {
2561
2592
  if (def === void 0 || def === null) return null;
2562
2593
  if (typeof def === "boolean") return def ? "true" : "false";
2563
2594
  if (typeof def === "number") return String(def);
2564
- if (typeof def === "string") return def;
2595
+ if (typeof def === "string") return `'${def}'`;
2565
2596
  return null;
2566
2597
  }
2567
2598
  function buildGormTags(field, isPk, isCompositePk, compositeIndexTags) {
@@ -2580,6 +2611,7 @@ function buildGormTags(field, isPk, isCompositePk, compositeIndexTags) {
2580
2611
  field.isUnique ? "uniqueIndex" : null,
2581
2612
  ...compositeIndexTags,
2582
2613
  includeNativeType ? `type:${nativeType}` : null,
2614
+ field.isList && field.kind !== "object" ? "serializer:json" : null,
2583
2615
  includeAutoCreate ? "autoCreateTime" : null,
2584
2616
  defaultVal !== null ? `default:${defaultVal}` : null,
2585
2617
  field.isUpdatedAt ? "autoUpdateTime" : null,
@@ -2587,14 +2619,16 @@ function buildGormTags(field, isPk, isCompositePk, compositeIndexTags) {
2587
2619
  ].filter((p) => p !== null).join(";")}" json:"${columnName}"\``;
2588
2620
  }
2589
2621
  function collectCompositeIndexTags(model, indexes) {
2622
+ const tableName = model.dbName ?? makeSnakeCase(model.name);
2590
2623
  const uniqueTags = model.uniqueFields.filter((fields) => fields.length > 1).flatMap((fields) => {
2591
- const idxName = `idx_${fields.map((f) => {
2624
+ const cols = fields.map((f) => {
2592
2625
  return model.fields.find((mf) => mf.name === f)?.dbName ?? makeSnakeCase(f);
2593
- }).join("_")}_unique`;
2626
+ });
2627
+ const idxName = `idx_${tableName}_${cols.join("_")}_unique`;
2594
2628
  return fields.map((f) => [f, `uniqueIndex:${idxName}`]);
2595
2629
  });
2596
2630
  const indexTags = indexes.filter((idx) => idx.model === model.name && (idx.type === "normal" || idx.type === "fulltext")).flatMap((idx) => {
2597
- const idxName = idx.dbName ?? idx.name ?? `idx_${idx.fields.map((f) => makeSnakeCase(f.name)).join("_")}`;
2631
+ const idxName = idx.dbName ?? idx.name ?? `idx_${tableName}_${idx.fields.map((f) => makeSnakeCase(f.name)).join("_")}`;
2598
2632
  return idx.fields.map((f) => [f.name, `index:${idxName}`]);
2599
2633
  });
2600
2634
  return [...uniqueTags, ...indexTags].reduce((map, [fieldName, tag]) => {
@@ -2659,7 +2693,8 @@ function goFieldName(name) {
2659
2693
  }
2660
2694
  function generateStructField(field, isPk, isCompositePk, compositeIndexTags, _enumNames) {
2661
2695
  const fieldName = goFieldName(field.name);
2662
- const goType = field.kind === "enum" ? field.isRequired ? "string" : "*string" : prismaTypeToGoType(field.type, field.isRequired);
2696
+ const scalarType = field.kind === "enum" ? field.isRequired ? "string" : "*string" : prismaTypeToGoType(field.type, field.isRequired);
2697
+ const goType = field.isList ? `[]${field.kind === "enum" ? "string" : prismaTypeToGoType(field.type, true)}` : scalarType;
2663
2698
  const tag = buildGormTags(field, isPk, isCompositePk, compositeIndexTags);
2664
2699
  return `\t${fieldName} ${goType}${tag ? ` ${tag}` : ""}`;
2665
2700
  }
@@ -2675,7 +2710,8 @@ function generateRelationFields(model, associations) {
2675
2710
  const fkFieldName = goFieldName(assoc.foreignKey);
2676
2711
  const refsFieldName = goFieldName(assoc.references);
2677
2712
  const tagParts = [fieldName !== assoc.targetModel || associations.belongsTo.filter((a) => a.targetModel === assoc.targetModel).length > 1 ? `foreignKey:${fkFieldName}` : null, needsReferencesTag(assoc.references) ? `references:${refsFieldName}` : null].filter((p) => p !== null);
2678
- return tagParts.length > 0 ? `\t${fieldName} ${assoc.targetModel} ${buildRelationTag(tagParts)}` : `\t${fieldName} ${assoc.targetModel}`;
2713
+ const targetType = assoc.targetModel === model.name ? `*${assoc.targetModel}` : assoc.targetModel;
2714
+ return tagParts.length > 0 ? `\t${fieldName} ${targetType} ${buildRelationTag(tagParts)}` : `\t${fieldName} ${targetType}`;
2679
2715
  });
2680
2716
  const hasManyLines = associations.hasMany.map((assoc) => {
2681
2717
  const tagParts = [`foreignKey:${goFieldName(assoc.foreignKey)}`, ...needsReferencesTag(assoc.references) ? [`references:${goFieldName(assoc.references)}`] : []];
@@ -2683,7 +2719,7 @@ function generateRelationFields(model, associations) {
2683
2719
  });
2684
2720
  const hasOneLines = associations.hasOne.map((assoc) => {
2685
2721
  const tagParts = [`foreignKey:${goFieldName(assoc.foreignKey)}`, ...needsReferencesTag(assoc.references) ? [`references:${goFieldName(assoc.references)}`] : []];
2686
- return `\t${goFieldName(assoc.name)} ${assoc.targetModel} ${buildRelationTag(tagParts)}`;
2722
+ return `\t${goFieldName(assoc.name)} *${assoc.targetModel} ${buildRelationTag(tagParts)}`;
2687
2723
  });
2688
2724
  const manyToManyLines = associations.manyToMany.map((assoc) => {
2689
2725
  const [leftName, rightName] = model.name < assoc.targetModel ? [model.name, assoc.targetModel] : [assoc.targetModel, model.name];
@@ -2707,7 +2743,9 @@ function generateModelStruct(model, allModels, enums, indexes) {
2707
2743
  const compositeTagMap = collectCompositeIndexTags(model, indexes);
2708
2744
  const tableName = model.dbName ?? makeSnakeCase(model.name);
2709
2745
  const fieldLines = model.fields.filter((f) => f.kind !== "object").map((field) => {
2710
- return generateStructField(field, field.isId || compositePkFieldNames.has(field.name), isCompositePk, compositeTagMap.get(field.name) ?? [], enumNames);
2746
+ const isPk = field.isId || compositePkFieldNames.has(field.name);
2747
+ const fieldIndexTags = compositeTagMap.get(field.name) ?? [];
2748
+ return generateStructField(field, isPk, isCompositePk, fieldIndexTags, enumNames);
2711
2749
  });
2712
2750
  const relationLines = generateRelationFields(model, associations);
2713
2751
  const tableNameMethod = tableName !== makeSnakeCase(model.name) ? [
@@ -2845,6 +2883,80 @@ function prismaTypeToRustType(type, isRequired) {
2845
2883
  if (!isRequired) return `Option<${base}>`;
2846
2884
  return base;
2847
2885
  }
2886
+ const RUST_KEYWORDS = /* @__PURE__ */ new Set([
2887
+ "as",
2888
+ "break",
2889
+ "const",
2890
+ "continue",
2891
+ "crate",
2892
+ "dyn",
2893
+ "else",
2894
+ "enum",
2895
+ "extern",
2896
+ "false",
2897
+ "fn",
2898
+ "for",
2899
+ "if",
2900
+ "impl",
2901
+ "in",
2902
+ "let",
2903
+ "loop",
2904
+ "match",
2905
+ "mod",
2906
+ "move",
2907
+ "mut",
2908
+ "pub",
2909
+ "ref",
2910
+ "return",
2911
+ "self",
2912
+ "Self",
2913
+ "static",
2914
+ "struct",
2915
+ "super",
2916
+ "trait",
2917
+ "true",
2918
+ "type",
2919
+ "unsafe",
2920
+ "use",
2921
+ "where",
2922
+ "while",
2923
+ "async",
2924
+ "await",
2925
+ "abstract",
2926
+ "become",
2927
+ "box",
2928
+ "do",
2929
+ "final",
2930
+ "macro",
2931
+ "override",
2932
+ "priv",
2933
+ "typeof",
2934
+ "unsized",
2935
+ "virtual",
2936
+ "yield",
2937
+ "try",
2938
+ "gen"
2939
+ ]);
2940
+ const RUST_NON_RAW = /* @__PURE__ */ new Set([
2941
+ "self",
2942
+ "Self",
2943
+ "crate",
2944
+ "super"
2945
+ ]);
2946
+ function rustFieldIdent(snake) {
2947
+ if (!RUST_KEYWORDS.has(snake)) return {
2948
+ ident: snake,
2949
+ derivedColumn: snake
2950
+ };
2951
+ if (RUST_NON_RAW.has(snake)) return {
2952
+ ident: `${snake}_`,
2953
+ derivedColumn: `${snake}_`
2954
+ };
2955
+ return {
2956
+ ident: `r#${snake}`,
2957
+ derivedColumn: snake
2958
+ };
2959
+ }
2848
2960
  function resolveSeaOrmColumnType(field) {
2849
2961
  if (!field.nativeType) return null;
2850
2962
  const [nativeName, nativeArgs] = field.nativeType;
@@ -2887,7 +2999,7 @@ function formatRustDefault(def) {
2887
2999
  if (typeof def === "string") return `"${def}"`;
2888
3000
  return null;
2889
3001
  }
2890
- function buildSeaOrmAttributes(field, isPk, isCompositePk) {
3002
+ function buildSeaOrmAttributes(field, isPk, isCompositePk, derivedColumn) {
2891
3003
  const attrs = [];
2892
3004
  if (isPk) {
2893
3005
  const parts = ["primary_key"];
@@ -2897,7 +3009,7 @@ function buildSeaOrmAttributes(field, isPk, isCompositePk) {
2897
3009
  if (field.isUnique) attrs.push("#[sea_orm(unique)]");
2898
3010
  const columnParts = [];
2899
3011
  const columnName = field.dbName ?? makeSnakeCase(field.name);
2900
- if (columnName !== makeSnakeCase(field.name)) columnParts.push(`column_name = "${columnName}"`);
3012
+ if (columnName !== (derivedColumn ?? makeSnakeCase(field.name))) columnParts.push(`column_name = "${columnName}"`);
2901
3013
  const colType = resolveSeaOrmColumnType(field);
2902
3014
  if (colType) columnParts.push(`column_type = "${colType}"`);
2903
3015
  if (!isPk || isCompositePk) {
@@ -2984,7 +3096,7 @@ function buildSerdeAttributes(opts) {
2984
3096
  }
2985
3097
  function generateEnum(e, serde = {}) {
2986
3098
  const variants = e.values.map((v) => {
2987
- const pascalName = v.name.charAt(0).toUpperCase() + v.name.slice(1).toLowerCase();
3099
+ const pascalName = v.name.split("_").filter((part) => part !== "").map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
2988
3100
  return ` #[sea_orm(string_value = "${v.name}")]\n ${pascalName},`;
2989
3101
  });
2990
3102
  return [
@@ -3025,9 +3137,10 @@ function generateRelationEnum(_model, associations) {
3025
3137
  }
3026
3138
  function generateRelatedImpls(model, associations) {
3027
3139
  const impls = [];
3028
- const m2mTargets = new Set(associations.manyToMany.map((a) => a.targetModel));
3140
+ const emittedTargets = /* @__PURE__ */ new Set();
3029
3141
  for (const assoc of associations.belongsTo) {
3030
- if (m2mTargets.has(assoc.targetModel)) continue;
3142
+ if (emittedTargets.has(assoc.targetModel)) continue;
3143
+ emittedTargets.add(assoc.targetModel);
3031
3144
  const targetModule = toModuleName(assoc.targetModel);
3032
3145
  impls.push([
3033
3146
  `impl Related<super::${targetModule}::Entity> for Entity {`,
@@ -3038,7 +3151,8 @@ function generateRelatedImpls(model, associations) {
3038
3151
  ].join("\n"));
3039
3152
  }
3040
3153
  for (const assoc of associations.hasMany) {
3041
- if (m2mTargets.has(assoc.targetModel)) continue;
3154
+ if (emittedTargets.has(assoc.targetModel)) continue;
3155
+ emittedTargets.add(assoc.targetModel);
3042
3156
  const targetModule = toModuleName(assoc.targetModel);
3043
3157
  impls.push([
3044
3158
  `impl Related<super::${targetModule}::Entity> for Entity {`,
@@ -3049,7 +3163,8 @@ function generateRelatedImpls(model, associations) {
3049
3163
  ].join("\n"));
3050
3164
  }
3051
3165
  for (const assoc of associations.hasOne) {
3052
- if (m2mTargets.has(assoc.targetModel)) continue;
3166
+ if (emittedTargets.has(assoc.targetModel)) continue;
3167
+ emittedTargets.add(assoc.targetModel);
3053
3168
  const targetModule = toModuleName(assoc.targetModel);
3054
3169
  impls.push([
3055
3170
  `impl Related<super::${targetModule}::Entity> for Entity {`,
@@ -3060,6 +3175,8 @@ function generateRelatedImpls(model, associations) {
3060
3175
  ].join("\n"));
3061
3176
  }
3062
3177
  for (const assoc of associations.manyToMany) {
3178
+ if (emittedTargets.has(assoc.targetModel)) continue;
3179
+ emittedTargets.add(assoc.targetModel);
3063
3180
  const targetModule = toModuleName(assoc.targetModel);
3064
3181
  const [leftName, rightName] = model.name < assoc.targetModel ? [model.name, assoc.targetModel] : [assoc.targetModel, model.name];
3065
3182
  const junctionModule = toSnakeCase(`${leftName}To${rightName}`);
@@ -3089,15 +3206,21 @@ function generateEntityFile(model, allModels, enums, serde = {}) {
3089
3206
  const scalarFields = model.fields.filter((f) => f.kind !== "object");
3090
3207
  const fieldLines = [];
3091
3208
  for (const field of scalarFields) {
3092
- const attrs = buildSeaOrmAttributes(field, field.isId || compositePkFieldNames.has(field.name), isCompositePk);
3093
- const rustType = enumNames.has(field.type) ? field.isRequired ? field.type : `Option<${field.type}>` : prismaTypeToRustType(field.type, field.isRequired);
3094
- const fieldName = toSnakeCase(field.name);
3209
+ const isPk = field.isId || compositePkFieldNames.has(field.name);
3210
+ const { ident: fieldName, derivedColumn } = rustFieldIdent(toSnakeCase(field.name));
3211
+ const attrs = buildSeaOrmAttributes(field, isPk, isCompositePk, derivedColumn);
3212
+ const elemType = enumNames.has(field.type) ? field.type : prismaTypeToRustType(field.type, true);
3213
+ const rustType = field.isList ? `Vec<${elemType}>` : enumNames.has(field.type) ? field.isRequired ? field.type : `Option<${field.type}>` : prismaTypeToRustType(field.type, field.isRequired);
3095
3214
  for (const attr of attrs) fieldLines.push(` ${attr}`);
3096
3215
  fieldLines.push(` pub ${fieldName}: ${rustType},`);
3097
3216
  }
3098
3217
  const relationEnum = generateRelationEnum(model, associations);
3099
3218
  const relatedImpls = generateRelatedImpls(model, associations);
3100
- const useLines = ["use sea_orm::entity::prelude::*;", "use serde::{Deserialize, Serialize};"];
3219
+ const useLines = [
3220
+ "use sea_orm::entity::prelude::*;",
3221
+ "use serde::{Deserialize, Serialize};",
3222
+ ...[...new Set(scalarFields.filter((f) => enumNames.has(f.type)).map((f) => f.type))].sort().map((name) => `use super::${toSnakeCase(name)}::${name};`)
3223
+ ];
3101
3224
  const deriveModel = canDeriveEq(scalarFields) ? "#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]" : "#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]";
3102
3225
  const serdeAttrs = buildSerdeAttributes(serde);
3103
3226
  return [
@@ -3253,7 +3376,7 @@ const PRISMA_TO_PYTHON = {
3253
3376
  Decimal: "Decimal",
3254
3377
  Boolean: "bool",
3255
3378
  DateTime: "datetime",
3256
- Json: "dict",
3379
+ Json: "dict[str, Any]",
3257
3380
  Bytes: "bytes"
3258
3381
  };
3259
3382
  const PRISMA_TO_SQLALCHEMY = {
@@ -3273,6 +3396,46 @@ function prismaTypeToSQLAlchemyType(type) {
3273
3396
  function prismaTypeToPythonType(type) {
3274
3397
  return PRISMA_TO_PYTHON[type] ?? "str";
3275
3398
  }
3399
+ const PYTHON_KEYWORDS = /* @__PURE__ */ new Set([
3400
+ "False",
3401
+ "None",
3402
+ "True",
3403
+ "and",
3404
+ "as",
3405
+ "assert",
3406
+ "async",
3407
+ "await",
3408
+ "break",
3409
+ "class",
3410
+ "continue",
3411
+ "def",
3412
+ "del",
3413
+ "elif",
3414
+ "else",
3415
+ "except",
3416
+ "finally",
3417
+ "for",
3418
+ "from",
3419
+ "global",
3420
+ "if",
3421
+ "import",
3422
+ "in",
3423
+ "is",
3424
+ "lambda",
3425
+ "nonlocal",
3426
+ "not",
3427
+ "or",
3428
+ "pass",
3429
+ "raise",
3430
+ "return",
3431
+ "try",
3432
+ "while",
3433
+ "with",
3434
+ "yield"
3435
+ ]);
3436
+ function pythonAttrName(columnName) {
3437
+ return PYTHON_KEYWORDS.has(columnName) ? `${columnName}_` : columnName;
3438
+ }
3276
3439
  function resolveNativeType(field) {
3277
3440
  const baseType = prismaTypeToSQLAlchemyType(field.type);
3278
3441
  if (!field.nativeType) return baseType;
@@ -3431,16 +3594,18 @@ function isAutoincrement(field) {
3431
3594
  function needsForeignKeysParam(targetModel, assocs) {
3432
3595
  return assocs.filter((a) => a.targetModel === targetModel).length > 1;
3433
3596
  }
3434
- function findBackPopulates(targetModelName, sourceModelName, foreignKey, allModels) {
3597
+ function findBackPopulates(targetModelName, sourceModelName, foreignKey, allModels, sourceFieldName) {
3435
3598
  const targetModel = allModels.find((m) => m.name === targetModelName);
3599
+ const sourceModel = allModels.find((m) => m.name === sourceModelName);
3436
3600
  if (!targetModel) return makeSnakeCase(sourceModelName);
3601
+ const relationName = (sourceModel?.fields.find((f) => f.kind === "object" && f.name === sourceFieldName))?.relationName;
3437
3602
  const backField = targetModel.fields.find((f) => {
3438
3603
  if (f.kind !== "object") return false;
3439
3604
  if (f.type !== sourceModelName) return false;
3605
+ if (targetModelName === sourceModelName && f.name === sourceFieldName) return false;
3606
+ if (relationName !== void 0) return f.relationName === relationName;
3440
3607
  if (f.relationFromFields?.includes(foreignKey)) return true;
3441
- const sourceModel = allModels.find((m) => m.name === sourceModelName);
3442
- if (!sourceModel) return false;
3443
- return sourceModel.fields.some((sf) => sf.relationName === f.relationName && sf.relationFromFields && sf.relationFromFields.includes(foreignKey));
3608
+ return sourceModel ? sourceModel.fields.some((sf) => sf.relationName === f.relationName && sf.relationFromFields?.includes(foreignKey)) : false;
3444
3609
  });
3445
3610
  return backField ? makeSnakeCase(backField.name) : makeSnakeCase(sourceModelName);
3446
3611
  }
@@ -3451,15 +3616,19 @@ function findM2MBackPopulates(targetModelName, sourceModelName, relationName, al
3451
3616
  return backField ? makeSnakeCase(backField.name) : makeSnakeCase(sourceModelName);
3452
3617
  }
3453
3618
  function generateColumn(field, isPk, isFk, associations, allModels, enumMap) {
3454
- const snakeName = field.dbName ?? makeSnakeCase(field.name);
3455
- const pythonType = field.kind === "enum" ? "str" : pythonTypeForNative(field);
3456
- const typeHint = field.isRequired ? pythonType : `Optional[${pythonType}]`;
3619
+ const columnName = field.dbName ?? makeSnakeCase(field.name);
3620
+ const attrName = pythonAttrName(columnName);
3621
+ const elemPythonType = field.kind === "enum" ? "str" : pythonTypeForNative(field);
3622
+ const pythonType = field.isList ? `list[${elemPythonType}]` : elemPythonType;
3623
+ const typeHint = field.isList || field.isRequired ? pythonType : `Optional[${pythonType}]`;
3457
3624
  const colArgs = [];
3625
+ if (attrName !== columnName) colArgs.push(`"${columnName}"`);
3458
3626
  if (field.kind === "enum") {
3459
3627
  const values = enumMap.get(field.type);
3460
- const valuesStr = values ? values.map((v) => `"${v}"`).join(", ") : "";
3461
- colArgs.push(`Enum(${valuesStr}, name="${makeSnakeCase(field.type)}")`);
3462
- } else if (needsExplicitSaType(field)) colArgs.push(resolveNativeType(field));
3628
+ const enumType = `Enum(${values ? values.map((v) => `"${v}"`).join(", ") : ""}, name="${makeSnakeCase(field.type)}")`;
3629
+ colArgs.push(field.isList ? `ARRAY(${enumType})` : enumType);
3630
+ } else if (field.isList) colArgs.push(`ARRAY(${prismaTypeToSQLAlchemyType(field.type)})`);
3631
+ else if (needsExplicitSaType(field)) colArgs.push(resolveNativeType(field));
3463
3632
  if (isFk) {
3464
3633
  const assoc = associations.belongsTo.find((a) => a.foreignKey === field.name);
3465
3634
  if (assoc) {
@@ -3477,8 +3646,8 @@ function generateColumn(field, isPk, isFk, associations, allModels, enumMap) {
3477
3646
  if (defaultVal !== null && !isPk) colArgs.push(`default=${defaultVal}`);
3478
3647
  }
3479
3648
  if (field.isUpdatedAt) colArgs.push("onupdate=func.now()");
3480
- if (colArgs.length === 0) return ` ${snakeName}: Mapped[${typeHint}]`;
3481
- return ` ${snakeName}: Mapped[${typeHint}] = mapped_column(${colArgs.join(", ")})`;
3649
+ if (colArgs.length === 0) return ` ${attrName}: Mapped[${typeHint}]`;
3650
+ return ` ${attrName}: Mapped[${typeHint}] = mapped_column(${colArgs.join(", ")})`;
3482
3651
  }
3483
3652
  function generateTableArgs(model, indexes) {
3484
3653
  const uniqueConstraints = model.uniqueFields.map((fields) => {
@@ -3504,15 +3673,17 @@ function generateBelongsToRelationships(associations, model, allModels) {
3504
3673
  return associations.belongsTo.map((assoc) => {
3505
3674
  const snakeName = makeSnakeCase(assoc.name);
3506
3675
  const snakeFk = model.fields.find((f) => f.name === assoc.foreignKey)?.dbName ?? makeSnakeCase(assoc.foreignKey);
3507
- const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels);
3676
+ const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels, assoc.name);
3508
3677
  const fkClause = needsForeignKeysParam(assoc.targetModel, associations.belongsTo) ? `foreign_keys=[${snakeFk}], ` : "";
3509
- return ` ${snakeName}: Mapped["${assoc.targetModel}"] = relationship(${fkClause}back_populates="${backPop}")`;
3678
+ const pkField = model.fields.find((f) => f.isId);
3679
+ const remoteClause = assoc.targetModel === model.name && pkField ? `remote_side=[${pkField.dbName ?? makeSnakeCase(pkField.name)}], ` : "";
3680
+ return ` ${snakeName}: Mapped["${assoc.targetModel}"] = relationship(${remoteClause}${fkClause}back_populates="${backPop}")`;
3510
3681
  });
3511
3682
  }
3512
3683
  function generateHasManyRelationships(associations, model, allModels) {
3513
3684
  return associations.hasMany.map((assoc) => {
3514
3685
  const snakeName = makeSnakeCase(assoc.name);
3515
- const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels);
3686
+ const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels, assoc.name);
3516
3687
  const targetModel = allModels.find((m) => m.name === assoc.targetModel);
3517
3688
  const needsFkParam = targetModel ? needsForeignKeysParam(model.name, getAssociations(targetModel, allModels).belongsTo) : false;
3518
3689
  const targetFkSnake = makeSnakeCase(assoc.foreignKey);
@@ -3523,7 +3694,7 @@ function generateHasManyRelationships(associations, model, allModels) {
3523
3694
  function generateHasOneRelationships(associations, model, allModels) {
3524
3695
  return associations.hasOne.map((assoc) => {
3525
3696
  const snakeName = makeSnakeCase(assoc.name);
3526
- const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels);
3697
+ const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels, assoc.name);
3527
3698
  const targetModel = allModels.find((m) => m.name === assoc.targetModel);
3528
3699
  const needsFkParam = targetModel ? needsForeignKeysParam(model.name, getAssociations(targetModel, allModels).belongsTo) : false;
3529
3700
  const targetFkSnake = makeSnakeCase(assoc.foreignKey);
@@ -3575,6 +3746,8 @@ function collectGlobalImports(models, _enums, indexes, m2mTables) {
3575
3746
  const needsOptional = models.some((m) => m.fields.some((f) => f.kind !== "object" && !f.isRequired));
3576
3747
  const hasRelationship = models.some((m) => m.fields.some((f) => f.kind === "object"));
3577
3748
  const needsFunc = models.some((m) => m.fields.some((f) => f.type === "DateTime" && isFunctionDefault(f.default) && f.default.name === "now" || f.isUpdatedAt));
3749
+ const needsAny = models.some((m) => m.fields.some((f) => f.type === "Json"));
3750
+ const needsArray = models.some((m) => m.fields.some((f) => f.kind !== "object" && f.isList));
3578
3751
  const needsDecimal = models.some((m) => m.fields.some((f) => f.type === "Decimal"));
3579
3752
  const needsDatetime = models.some((m) => m.fields.some((f) => f.type === "DateTime"));
3580
3753
  const needsUuid = models.some((m) => m.fields.some((f) => {
@@ -3599,6 +3772,7 @@ function collectGlobalImports(models, _enums, indexes, m2mTables) {
3599
3772
  saImports.add("Enum");
3600
3773
  continue;
3601
3774
  }
3775
+ if (field.isList) saImports.add(prismaTypeToSQLAlchemyType(field.type));
3602
3776
  if (needsExplicitSaType(field)) {
3603
3777
  const resolved = resolveNativeType(field);
3604
3778
  saImports.add(resolved.replace(/\(.*\)$/, ""));
@@ -3624,6 +3798,7 @@ function collectGlobalImports(models, _enums, indexes, m2mTables) {
3624
3798
  }
3625
3799
  }
3626
3800
  if (needsFunc) saImports.add("func");
3801
+ if (needsArray) saImports.add("ARRAY");
3627
3802
  const lines = [];
3628
3803
  const sortedSa = [...saImports].sort();
3629
3804
  if (sortedSa.length > 0) lines.push(`from sqlalchemy import ${sortedSa.join(", ")}`);
@@ -3634,7 +3809,8 @@ function collectGlobalImports(models, _enums, indexes, m2mTables) {
3634
3809
  ];
3635
3810
  if (hasRelationship) ormImports.push("relationship");
3636
3811
  lines.push(`from sqlalchemy.orm import ${ormImports.sort().join(", ")}`);
3637
- if (needsOptional) lines.push("from typing import Optional");
3812
+ const typingImports = [needsAny ? "Any" : null, needsOptional ? "Optional" : null].filter((i) => i !== null);
3813
+ if (typingImports.length > 0) lines.push(`from typing import ${typingImports.join(", ")}`);
3638
3814
  if (needsDecimal) lines.push("from decimal import Decimal as DecimalType");
3639
3815
  const dtParts = [];
3640
3816
  if (needsDatetime) dtParts.push("datetime");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hekireki",
3
- "version": "0.8.6",
3
+ "version": "0.9.0",
4
4
  "description": "Hekireki is a tool that generates validation schemas for Zod, Valibot, ArkType, and Effect Schema, as well as ER diagrams and DBML, from Prisma schemas annotated with comments.",
5
5
  "keywords": [
6
6
  "ajv",
@@ -51,26 +51,24 @@
51
51
  "dist"
52
52
  ],
53
53
  "type": "module",
54
- "publishConfig": {
55
- "access": "public"
56
- },
57
54
  "dependencies": {
58
55
  "@hono/node-server": "^2.0.6",
59
56
  "@prisma/generator-helper": "^7.8.0",
60
57
  "@resvg/resvg-js": "^2.6.2",
61
58
  "@softwaretechnik/dbml-renderer": "^1.0.31",
62
59
  "hono": "^4.12.27",
63
- "oxfmt": "^0.56.0"
60
+ "oxfmt": "^0.57.0"
64
61
  },
65
62
  "devDependencies": {
66
63
  "@paralleldrive/cuid2": "^3.3.0",
67
64
  "@prisma/client": "^7.8.0",
68
65
  "@sinclair/typebox": "^0.34.49",
69
- "@types/node": "^26.0.1",
70
- "@typescript/native-preview": "7.0.0-dev.20260624.1",
66
+ "@types/node": "^26.1.0",
67
+ "@typescript/native-preview": "7.0.0-dev.20260701.1",
71
68
  "ajv": "^8.20.0",
72
- "arktype": "^2.2.1",
73
- "better-auth": "^1.6.20",
69
+ "arktype": "^2.2.2",
70
+ "better-auth": "^1.6.23",
71
+ "cuid": "^3.0.0",
74
72
  "drizzle-kit": "^0.31.10",
75
73
  "drizzle-orm": "^0.45.2",
76
74
  "effect": "^3.21.4",
@@ -79,13 +77,13 @@
79
77
  "tsdown": "^0.22.3",
80
78
  "tsx": "^4.22.4",
81
79
  "typescript": "^6.0.3",
82
- "valibot": "1.4.1",
80
+ "valibot": "1.4.2",
83
81
  "zod": "^4.4.3"
84
82
  },
85
83
  "scripts": {
86
84
  "generate": "prisma generate",
87
85
  "deps": "rm -rf node_modules && pnpm install",
88
86
  "build": "tsdown",
89
- "release": "npm pkg fix && pnpm build && npm publish"
87
+ "conformance": "pnpm build && bash conformance/check.sh"
90
88
  }
91
89
  }