hekireki 0.8.5 → 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/README.md CHANGED
@@ -530,6 +530,26 @@ export const postRelations = relations(post, ({ one }) => ({
530
530
  }))
531
531
  ```
532
532
 
533
+ #### ID default functions
534
+
535
+ For Drizzle, Prisma `@default(...)` ID generators are emitted as client-side `$defaultFn` calls. Some require an extra package — install it in your project:
536
+
537
+ | Prisma default | Generated call | Package to install |
538
+ | ---------------------------------------- | --------------------- | ---------------------------------------------- |
539
+ | `@default(uuid())` / `@default(uuid(4))` | `crypto.randomUUID()` | — (Node/Web built-in) |
540
+ | `@default(cuid())` / `@default(cuid(1))` | `cuid()` | `npm i cuid` |
541
+ | `@default(cuid(2))` | `createId()` | `npm i @paralleldrive/cuid2` |
542
+ | `@default(nanoid())` | `nanoid()` | `npm i nanoid` (>= 5.0.9, or >= 3.3.8 for CJS) |
543
+ | `@default(ulid())` | `ulid()` | `npm i ulidx` |
544
+
545
+ Notes:
546
+
547
+ - **`cuid()` uses cuid v1**, which its author has deprecated in favor of cuid v2 ([npm `cuid`](https://www.npmjs.com/package/cuid)). It is emitted only when you explicitly write `@default(cuid())`; for new schemas prefer `@default(cuid(2))`.
548
+ - **`ulid()` maps to [`ulidx`](https://www.npmjs.com/package/ulidx)** (an actively maintained, crypto-backed drop-in for `ulid`).
549
+ - `@default(uuid(7))` is currently emitted as `crypto.randomUUID()` (UUID v4); native v7 support is planned.
550
+ - `@default(nanoid(n))` length argument is not yet applied — the default length is used.
551
+ - Inserts that bypass the ORM (raw SQL, bulk load) won't run `$defaultFn`; add a database-level `DEFAULT` for those paths.
552
+
533
553
  ### Mermaid
534
554
 
535
555
  ```mermaid
package/dist/bin/ajv.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerGenerator } from "../bin-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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-Dcg9ZO7c.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
@@ -163,7 +163,7 @@ function parseDocumentWithoutAnnotations(documentation) {
163
163
  "@t.",
164
164
  "@j."
165
165
  ];
166
- const annotationExact = new Set([
166
+ const annotationExact = /* @__PURE__ */ new Set([
167
167
  "@z",
168
168
  "@v",
169
169
  "@a",
@@ -183,7 +183,7 @@ function stripAnnotations(doc) {
183
183
  "@t.",
184
184
  "@j."
185
185
  ];
186
- const annotationExact = new Set([
186
+ const annotationExact = /* @__PURE__ */ new Set([
187
187
  "@z",
188
188
  "@v",
189
189
  "@a",
@@ -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
@@ -1067,7 +1069,7 @@ const ModelAction = {
1067
1069
  function isFieldDefault$1(v) {
1068
1070
  return typeof v === "object" && v !== null && "name" in v && "args" in v;
1069
1071
  }
1070
- const fieldDirectiveMap = new Map([
1072
+ const fieldDirectiveMap = /* @__PURE__ */ new Map([
1071
1073
  ["isUnique", "@unique"],
1072
1074
  ["isId", "@id"],
1073
1075
  ["hasDefaultValue", "@default"],
@@ -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
@@ -1761,17 +1765,29 @@ function createImports() {
1761
1765
  ext: /* @__PURE__ */ new Map()
1762
1766
  };
1763
1767
  }
1764
- function addExternal(imports, pkg, fn) {
1765
- const fns = imports.ext.get(pkg) ?? /* @__PURE__ */ new Set();
1766
- fns.add(fn);
1767
- imports.ext.set(pkg, fns);
1768
+ function applyImport(imports, req) {
1769
+ if (req.pkg === "drizzle-orm") {
1770
+ imports.orm.add(req.name);
1771
+ return;
1772
+ }
1773
+ const entry = imports.ext.get(req.pkg) ?? { named: /* @__PURE__ */ new Set() };
1774
+ const next = req.kind === "default" ? {
1775
+ named: entry.named,
1776
+ default: req.name
1777
+ } : {
1778
+ named: entry.named.add(req.name),
1779
+ default: entry.default
1780
+ };
1781
+ imports.ext.set(req.pkg, next);
1768
1782
  }
1769
1783
  function generateImports(imports, provider) {
1770
1784
  const mod = provider === "postgresql" ? "drizzle-orm/pg-core" : provider === "mysql" ? "drizzle-orm/mysql-core" : "drizzle-orm/sqlite-core";
1771
1785
  return [
1772
1786
  imports.core.size > 0 ? `import { ${[...imports.core].sort().join(", ")} } from '${mod}'` : "",
1773
1787
  imports.orm.size > 0 ? `import { ${[...imports.orm].sort().join(", ")} } from 'drizzle-orm'` : "",
1774
- ...[...imports.ext.entries()].map(([pkg, fns]) => `import { ${[...fns].sort().join(", ")} } from '${pkg}'`)
1788
+ ...[...imports.ext.entries()].map(([pkg, entry]) => {
1789
+ return `import ${[entry.default, entry.named.size > 0 ? `{ ${[...entry.named].sort().join(", ")} }` : void 0].filter((c) => c !== void 0).join(", ")} from '${pkg}'`;
1790
+ })
1775
1791
  ].filter(Boolean).join("\n");
1776
1792
  }
1777
1793
  function snakeToCamel(name) {
@@ -1790,6 +1806,18 @@ function resolveVarNameByType(type, models) {
1790
1806
  function isFieldDefault(v) {
1791
1807
  return typeof v === "object" && v !== null && "name" in v;
1792
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
+ }
1793
1821
  function resolveScalarType(field, provider) {
1794
1822
  if (field.nativeType && provider !== "sqlite") {
1795
1823
  const [nativeName, nativeArgs] = field.nativeType;
@@ -1804,10 +1832,7 @@ function makeColumnExpr(field, provider, imports, enums) {
1804
1832
  if (field.kind === "enum") {
1805
1833
  const enumDef = enums.find((e) => e.name === field.type);
1806
1834
  const enumValues = enumDef ? enumDef.values.map((v) => `'${v.name}'`).join(", ") : "";
1807
- if (provider === "postgresql") {
1808
- imports.core.add("pgEnum");
1809
- return `pgEnum('${enumDef?.dbName ?? field.type}', [${enumValues}])('${colName}')`;
1810
- }
1835
+ if (provider === "postgresql") return `${enumIdentifier(field.type)}('${colName}')`;
1811
1836
  if (provider === "mysql") {
1812
1837
  imports.core.add("mysqlEnum");
1813
1838
  return `mysqlEnum('${colName}', [${enumValues}])`;
@@ -1828,80 +1853,98 @@ function makeColumnExpr(field, provider, imports, enums) {
1828
1853
  const rest = baseExpr.slice(parenIdx + 1);
1829
1854
  return rest === ")" ? `${baseFnName}('${colName}')` : `${baseFnName}('${colName}', ${rest}`;
1830
1855
  }
1856
+ const SQL_IMPORT = {
1857
+ pkg: "drizzle-orm",
1858
+ kind: "named",
1859
+ name: "sql"
1860
+ };
1831
1861
  function resolveDefaultValue(dflt, fieldType, provider) {
1832
1862
  if (dflt === void 0 || dflt === null) return {
1833
1863
  chain: "",
1834
- needsSql: false,
1835
- needsCuid: false
1864
+ imports: []
1836
1865
  };
1837
1866
  if (isFieldDefault(dflt)) switch (dflt.name) {
1838
1867
  case "autoincrement": return {
1839
1868
  chain: "",
1840
- needsSql: false,
1841
- needsCuid: false
1869
+ imports: []
1842
1870
  };
1843
1871
  case "now":
1844
1872
  if (provider === "sqlite") return {
1845
1873
  chain: ".default(sql`(unixepoch() * 1000)`)",
1846
- needsSql: true,
1847
- needsCuid: false
1874
+ imports: [SQL_IMPORT]
1848
1875
  };
1849
1876
  if (provider === "mysql") return {
1850
1877
  chain: ".default(sql`CURRENT_TIMESTAMP(3)`)",
1851
- needsSql: true,
1852
- needsCuid: false
1878
+ imports: [SQL_IMPORT]
1853
1879
  };
1854
1880
  return {
1855
1881
  chain: ".defaultNow()",
1856
- needsSql: false,
1857
- needsCuid: false
1882
+ imports: []
1858
1883
  };
1859
1884
  case "uuid": return {
1860
1885
  chain: ".$defaultFn(() => crypto.randomUUID())",
1861
- needsSql: false,
1862
- needsCuid: false
1886
+ imports: []
1863
1887
  };
1864
- case "cuid": return {
1888
+ case "cuid": return dflt.args[0] === 2 ? {
1865
1889
  chain: ".$defaultFn(() => createId())",
1866
- needsSql: false,
1867
- needsCuid: true
1890
+ imports: [{
1891
+ pkg: "@paralleldrive/cuid2",
1892
+ kind: "named",
1893
+ name: "createId"
1894
+ }]
1895
+ } : {
1896
+ chain: ".$defaultFn(() => cuid())",
1897
+ imports: [{
1898
+ pkg: "cuid",
1899
+ kind: "default",
1900
+ name: "cuid"
1901
+ }]
1902
+ };
1903
+ case "nanoid": return {
1904
+ chain: ".$defaultFn(() => nanoid())",
1905
+ imports: [{
1906
+ pkg: "nanoid",
1907
+ kind: "named",
1908
+ name: "nanoid"
1909
+ }]
1910
+ };
1911
+ case "ulid": return {
1912
+ chain: ".$defaultFn(() => ulid())",
1913
+ imports: [{
1914
+ pkg: "ulidx",
1915
+ kind: "named",
1916
+ name: "ulid"
1917
+ }]
1868
1918
  };
1869
1919
  case "dbgenerated":
1870
1920
  if (typeof dflt.args[0] === "string") return {
1871
1921
  chain: `.default(sql\`${dflt.args[0]}\`)`,
1872
- needsSql: true,
1873
- needsCuid: false
1922
+ imports: [SQL_IMPORT]
1874
1923
  };
1875
1924
  return {
1876
1925
  chain: "",
1877
- needsSql: false,
1878
- needsCuid: false
1926
+ imports: []
1879
1927
  };
1880
1928
  default: return {
1881
1929
  chain: "",
1882
- needsSql: false,
1883
- needsCuid: false
1930
+ imports: []
1884
1931
  };
1885
1932
  }
1886
1933
  if (typeof dflt === "string") return {
1887
1934
  chain: `.default('${dflt}')`,
1888
- needsSql: false,
1889
- needsCuid: false
1935
+ imports: []
1890
1936
  };
1891
1937
  if (typeof dflt === "number") return {
1892
1938
  chain: fieldType === "Decimal" ? `.default('${dflt}')` : `.default(${dflt})`,
1893
- needsSql: false,
1894
- needsCuid: false
1939
+ imports: []
1895
1940
  };
1896
1941
  if (typeof dflt === "boolean") return {
1897
1942
  chain: `.default(${dflt})`,
1898
- needsSql: false,
1899
- needsCuid: false
1943
+ imports: []
1900
1944
  };
1901
1945
  return {
1902
1946
  chain: "",
1903
- needsSql: false,
1904
- needsCuid: false
1947
+ imports: []
1905
1948
  };
1906
1949
  }
1907
1950
  function resolveUpdatedAtDefault(provider) {
@@ -1920,8 +1963,7 @@ function resolveUpdatedAtDefault(provider) {
1920
1963
  }
1921
1964
  function makeDefaultChain(dflt, fieldType, provider, imports) {
1922
1965
  const result = resolveDefaultValue(dflt, fieldType, provider);
1923
- if (result.needsSql) imports.orm.add("sql");
1924
- if (result.needsCuid) addExternal(imports, "@paralleldrive/cuid2", "createId");
1966
+ for (const req of result.imports) applyImport(imports, req);
1925
1967
  return result.chain;
1926
1968
  }
1927
1969
  const PRISMA_ACTION_MAP = {
@@ -1948,6 +1990,7 @@ function makeColumn(field, model, models, provider, imports, enums) {
1948
1990
  const hasCompositePK = model.primaryKey !== null;
1949
1991
  const colExpr = makeColumnExpr(field, provider, imports, enums);
1950
1992
  const chain = [
1993
+ field.isList && field.kind === "scalar" && provider === "postgresql" ? ".array()" : "",
1951
1994
  field.isId && !hasCompositePK ? isAutoincrement && provider === "sqlite" ? ".primaryKey({ autoIncrement: true })" : ".primaryKey()" : "",
1952
1995
  field.isRequired && !field.isId && !(isAutoincrement && provider === "postgresql") ? ".notNull()" : "",
1953
1996
  field.isUnique ? ".unique()" : "",
@@ -1957,8 +2000,7 @@ function makeColumn(field, model, models, provider, imports, enums) {
1957
2000
  if (r.needsSql) imports.orm.add("sql");
1958
2001
  return r.chain;
1959
2002
  })() : makeDefaultChain(field.default, field.type, provider, imports),
1960
- field.isUpdatedAt ? ".$onUpdate(() => new Date())" : "",
1961
- field.isList && field.kind === "scalar" && provider === "postgresql" ? ".array()" : ""
2003
+ field.isUpdatedAt ? ".$onUpdate(() => new Date())" : ""
1962
2004
  ].join("");
1963
2005
  return `${field.name}: ${colExpr}${chain}`;
1964
2006
  }
@@ -2026,6 +2068,7 @@ function makeRelations(models, imports) {
2026
2068
  function drizzleSchema(datamodel, provider, indexes) {
2027
2069
  const db = resolveDbProvider(provider);
2028
2070
  const imports = createImports();
2071
+ const enumLines = makeEnumDeclarations(datamodel.models, datamodel.enums, db, imports);
2029
2072
  const tableLines = datamodel.models.map((model) => makeTable(model, datamodel.models, db, imports, datamodel.enums, indexes));
2030
2073
  const relationsLines = makeRelations(datamodel.models, imports);
2031
2074
  const tableLinesWithGap = tableLines.flatMap((line, i) => i < tableLines.length - 1 ? [line, ""] : [line]);
@@ -2033,6 +2076,7 @@ function drizzleSchema(datamodel, provider, indexes) {
2033
2076
  return [
2034
2077
  generateImports(imports, db),
2035
2078
  "",
2079
+ ...enumLines.length > 0 ? [...enumLines, ""] : [],
2036
2080
  ...tableLinesWithGap,
2037
2081
  ...relationsLinesWithGap.length > 0 ? ["", ...relationsLinesWithGap] : []
2038
2082
  ].join("\n");
@@ -2169,6 +2213,7 @@ function getAssociations$3(model, allModels) {
2169
2213
  const belongsTo = [];
2170
2214
  const hasMany = [];
2171
2215
  const hasOne = [];
2216
+ const manyToMany = [];
2172
2217
  for (const field of model.fields) {
2173
2218
  if (field.kind !== "object") continue;
2174
2219
  if (field.relationFromFields && field.relationFromFields.length > 0) {
@@ -2184,7 +2229,15 @@ function getAssociations$3(model, allModels) {
2184
2229
  const targetModel = allModels.find((m) => m.name === field.type);
2185
2230
  if (!targetModel) continue;
2186
2231
  if (field.isList) {
2187
- 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
+ }
2188
2241
  }
2189
2242
  const foreignKey = targetModel.fields.find((f) => f.relationName === field.relationName && f.relationFromFields && f.relationFromFields.length > 0)?.relationFromFields?.[0];
2190
2243
  if (!foreignKey) continue;
@@ -2202,7 +2255,8 @@ function getAssociations$3(model, allModels) {
2202
2255
  return {
2203
2256
  belongsTo,
2204
2257
  hasMany,
2205
- hasOne
2258
+ hasOne,
2259
+ manyToMany
2206
2260
  };
2207
2261
  }
2208
2262
  function ectoSchemas(models, app, allModels, enums) {
@@ -2242,7 +2296,8 @@ function ectoSchemas(models, app, allModels, enums) {
2242
2296
  }),
2243
2297
  ...associations.belongsTo.map((a) => `${makeSnakeCase(a.name)}: ${appName}.${a.targetModel}.t() | nil`),
2244
2298
  ...associations.hasOne.map((a) => `${makeSnakeCase(a.name)}: ${appName}.${a.targetModel}.t() | nil`),
2245
- ...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()]`)
2246
2301
  ];
2247
2302
  const typeSpecLines = [
2248
2303
  " @type t :: %__MODULE__{",
@@ -2265,7 +2320,8 @@ function ectoSchemas(models, app, allModels, enums) {
2265
2320
  const defaultOpt = ((def) => {
2266
2321
  if (def === void 0 || def === null) return null;
2267
2322
  if (typeof def === "string") return `default: "${def}"`;
2268
- 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}`;
2269
2325
  return null;
2270
2326
  })(f.default);
2271
2327
  return ` field(:${snakeName}, ${ectoType}${primary}${defaultOpt ? `, ${defaultOpt}` : ""}${sourceOpt})`;
@@ -2302,6 +2358,9 @@ function ectoSchemas(models, app, allModels, enums) {
2302
2358
  const snakeFk = makeSnakeCase(a.foreignKey);
2303
2359
  return ` has_many(:${makeSnakeCase(a.name)}, ${appName}.${a.targetModel}, foreign_key: :${snakeFk})`;
2304
2360
  });
2361
+ const manyToManyLines = associations.manyToMany.map((a) => {
2362
+ return ` many_to_many(:${makeSnakeCase(a.name)}, ${appName}.${a.targetModel}, join_through: "${a.joinThrough}")`;
2363
+ });
2305
2364
  return [
2306
2365
  `defmodule ${appName}.${model.name} do`,
2307
2366
  " use Ecto.Schema",
@@ -2322,6 +2381,7 @@ function ectoSchemas(models, app, allModels, enums) {
2322
2381
  ...belongsToLines,
2323
2382
  ...hasOneLines,
2324
2383
  ...hasManyLines,
2384
+ ...manyToManyLines,
2325
2385
  ...timestampsLine ? [timestampsLine] : [],
2326
2386
  " end",
2327
2387
  "end"
@@ -2532,7 +2592,7 @@ function formatGoDefault(def) {
2532
2592
  if (def === void 0 || def === null) return null;
2533
2593
  if (typeof def === "boolean") return def ? "true" : "false";
2534
2594
  if (typeof def === "number") return String(def);
2535
- if (typeof def === "string") return def;
2595
+ if (typeof def === "string") return `'${def}'`;
2536
2596
  return null;
2537
2597
  }
2538
2598
  function buildGormTags(field, isPk, isCompositePk, compositeIndexTags) {
@@ -2551,6 +2611,7 @@ function buildGormTags(field, isPk, isCompositePk, compositeIndexTags) {
2551
2611
  field.isUnique ? "uniqueIndex" : null,
2552
2612
  ...compositeIndexTags,
2553
2613
  includeNativeType ? `type:${nativeType}` : null,
2614
+ field.isList && field.kind !== "object" ? "serializer:json" : null,
2554
2615
  includeAutoCreate ? "autoCreateTime" : null,
2555
2616
  defaultVal !== null ? `default:${defaultVal}` : null,
2556
2617
  field.isUpdatedAt ? "autoUpdateTime" : null,
@@ -2558,14 +2619,16 @@ function buildGormTags(field, isPk, isCompositePk, compositeIndexTags) {
2558
2619
  ].filter((p) => p !== null).join(";")}" json:"${columnName}"\``;
2559
2620
  }
2560
2621
  function collectCompositeIndexTags(model, indexes) {
2622
+ const tableName = model.dbName ?? makeSnakeCase(model.name);
2561
2623
  const uniqueTags = model.uniqueFields.filter((fields) => fields.length > 1).flatMap((fields) => {
2562
- const idxName = `idx_${fields.map((f) => {
2624
+ const cols = fields.map((f) => {
2563
2625
  return model.fields.find((mf) => mf.name === f)?.dbName ?? makeSnakeCase(f);
2564
- }).join("_")}_unique`;
2626
+ });
2627
+ const idxName = `idx_${tableName}_${cols.join("_")}_unique`;
2565
2628
  return fields.map((f) => [f, `uniqueIndex:${idxName}`]);
2566
2629
  });
2567
2630
  const indexTags = indexes.filter((idx) => idx.model === model.name && (idx.type === "normal" || idx.type === "fulltext")).flatMap((idx) => {
2568
- 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("_")}`;
2569
2632
  return idx.fields.map((f) => [f.name, `index:${idxName}`]);
2570
2633
  });
2571
2634
  return [...uniqueTags, ...indexTags].reduce((map, [fieldName, tag]) => {
@@ -2574,7 +2637,7 @@ function collectCompositeIndexTags(model, indexes) {
2574
2637
  return map;
2575
2638
  }, /* @__PURE__ */ new Map());
2576
2639
  }
2577
- const GO_INITIALISMS = new Set([
2640
+ const GO_INITIALISMS = /* @__PURE__ */ new Set([
2578
2641
  "acl",
2579
2642
  "api",
2580
2643
  "ascii",
@@ -2630,7 +2693,8 @@ function goFieldName(name) {
2630
2693
  }
2631
2694
  function generateStructField(field, isPk, isCompositePk, compositeIndexTags, _enumNames) {
2632
2695
  const fieldName = goFieldName(field.name);
2633
- 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;
2634
2698
  const tag = buildGormTags(field, isPk, isCompositePk, compositeIndexTags);
2635
2699
  return `\t${fieldName} ${goType}${tag ? ` ${tag}` : ""}`;
2636
2700
  }
@@ -2646,7 +2710,8 @@ function generateRelationFields(model, associations) {
2646
2710
  const fkFieldName = goFieldName(assoc.foreignKey);
2647
2711
  const refsFieldName = goFieldName(assoc.references);
2648
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);
2649
- 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}`;
2650
2715
  });
2651
2716
  const hasManyLines = associations.hasMany.map((assoc) => {
2652
2717
  const tagParts = [`foreignKey:${goFieldName(assoc.foreignKey)}`, ...needsReferencesTag(assoc.references) ? [`references:${goFieldName(assoc.references)}`] : []];
@@ -2654,7 +2719,7 @@ function generateRelationFields(model, associations) {
2654
2719
  });
2655
2720
  const hasOneLines = associations.hasOne.map((assoc) => {
2656
2721
  const tagParts = [`foreignKey:${goFieldName(assoc.foreignKey)}`, ...needsReferencesTag(assoc.references) ? [`references:${goFieldName(assoc.references)}`] : []];
2657
- return `\t${goFieldName(assoc.name)} ${assoc.targetModel} ${buildRelationTag(tagParts)}`;
2722
+ return `\t${goFieldName(assoc.name)} *${assoc.targetModel} ${buildRelationTag(tagParts)}`;
2658
2723
  });
2659
2724
  const manyToManyLines = associations.manyToMany.map((assoc) => {
2660
2725
  const [leftName, rightName] = model.name < assoc.targetModel ? [model.name, assoc.targetModel] : [assoc.targetModel, model.name];
@@ -2678,7 +2743,9 @@ function generateModelStruct(model, allModels, enums, indexes) {
2678
2743
  const compositeTagMap = collectCompositeIndexTags(model, indexes);
2679
2744
  const tableName = model.dbName ?? makeSnakeCase(model.name);
2680
2745
  const fieldLines = model.fields.filter((f) => f.kind !== "object").map((field) => {
2681
- 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);
2682
2749
  });
2683
2750
  const relationLines = generateRelationFields(model, associations);
2684
2751
  const tableNameMethod = tableName !== makeSnakeCase(model.name) ? [
@@ -2816,6 +2883,80 @@ function prismaTypeToRustType(type, isRequired) {
2816
2883
  if (!isRequired) return `Option<${base}>`;
2817
2884
  return base;
2818
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
+ }
2819
2960
  function resolveSeaOrmColumnType(field) {
2820
2961
  if (!field.nativeType) return null;
2821
2962
  const [nativeName, nativeArgs] = field.nativeType;
@@ -2858,7 +2999,7 @@ function formatRustDefault(def) {
2858
2999
  if (typeof def === "string") return `"${def}"`;
2859
3000
  return null;
2860
3001
  }
2861
- function buildSeaOrmAttributes(field, isPk, isCompositePk) {
3002
+ function buildSeaOrmAttributes(field, isPk, isCompositePk, derivedColumn) {
2862
3003
  const attrs = [];
2863
3004
  if (isPk) {
2864
3005
  const parts = ["primary_key"];
@@ -2868,7 +3009,7 @@ function buildSeaOrmAttributes(field, isPk, isCompositePk) {
2868
3009
  if (field.isUnique) attrs.push("#[sea_orm(unique)]");
2869
3010
  const columnParts = [];
2870
3011
  const columnName = field.dbName ?? makeSnakeCase(field.name);
2871
- if (columnName !== makeSnakeCase(field.name)) columnParts.push(`column_name = "${columnName}"`);
3012
+ if (columnName !== (derivedColumn ?? makeSnakeCase(field.name))) columnParts.push(`column_name = "${columnName}"`);
2872
3013
  const colType = resolveSeaOrmColumnType(field);
2873
3014
  if (colType) columnParts.push(`column_type = "${colType}"`);
2874
3015
  if (!isPk || isCompositePk) {
@@ -2943,7 +3084,7 @@ function toPascalCase(name) {
2943
3084
  function toModuleName(modelName) {
2944
3085
  return toSnakeCase(modelName);
2945
3086
  }
2946
- const NON_EQ_PRISMA_TYPES = new Set(["Float"]);
3087
+ const NON_EQ_PRISMA_TYPES = /* @__PURE__ */ new Set(["Float"]);
2947
3088
  function canDeriveEq(fields) {
2948
3089
  return fields.filter((f) => f.kind !== "object").every((f) => !NON_EQ_PRISMA_TYPES.has(f.type));
2949
3090
  }
@@ -2955,7 +3096,7 @@ function buildSerdeAttributes(opts) {
2955
3096
  }
2956
3097
  function generateEnum(e, serde = {}) {
2957
3098
  const variants = e.values.map((v) => {
2958
- 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("");
2959
3100
  return ` #[sea_orm(string_value = "${v.name}")]\n ${pascalName},`;
2960
3101
  });
2961
3102
  return [
@@ -2996,9 +3137,10 @@ function generateRelationEnum(_model, associations) {
2996
3137
  }
2997
3138
  function generateRelatedImpls(model, associations) {
2998
3139
  const impls = [];
2999
- const m2mTargets = new Set(associations.manyToMany.map((a) => a.targetModel));
3140
+ const emittedTargets = /* @__PURE__ */ new Set();
3000
3141
  for (const assoc of associations.belongsTo) {
3001
- if (m2mTargets.has(assoc.targetModel)) continue;
3142
+ if (emittedTargets.has(assoc.targetModel)) continue;
3143
+ emittedTargets.add(assoc.targetModel);
3002
3144
  const targetModule = toModuleName(assoc.targetModel);
3003
3145
  impls.push([
3004
3146
  `impl Related<super::${targetModule}::Entity> for Entity {`,
@@ -3009,7 +3151,8 @@ function generateRelatedImpls(model, associations) {
3009
3151
  ].join("\n"));
3010
3152
  }
3011
3153
  for (const assoc of associations.hasMany) {
3012
- if (m2mTargets.has(assoc.targetModel)) continue;
3154
+ if (emittedTargets.has(assoc.targetModel)) continue;
3155
+ emittedTargets.add(assoc.targetModel);
3013
3156
  const targetModule = toModuleName(assoc.targetModel);
3014
3157
  impls.push([
3015
3158
  `impl Related<super::${targetModule}::Entity> for Entity {`,
@@ -3020,7 +3163,8 @@ function generateRelatedImpls(model, associations) {
3020
3163
  ].join("\n"));
3021
3164
  }
3022
3165
  for (const assoc of associations.hasOne) {
3023
- if (m2mTargets.has(assoc.targetModel)) continue;
3166
+ if (emittedTargets.has(assoc.targetModel)) continue;
3167
+ emittedTargets.add(assoc.targetModel);
3024
3168
  const targetModule = toModuleName(assoc.targetModel);
3025
3169
  impls.push([
3026
3170
  `impl Related<super::${targetModule}::Entity> for Entity {`,
@@ -3031,6 +3175,8 @@ function generateRelatedImpls(model, associations) {
3031
3175
  ].join("\n"));
3032
3176
  }
3033
3177
  for (const assoc of associations.manyToMany) {
3178
+ if (emittedTargets.has(assoc.targetModel)) continue;
3179
+ emittedTargets.add(assoc.targetModel);
3034
3180
  const targetModule = toModuleName(assoc.targetModel);
3035
3181
  const [leftName, rightName] = model.name < assoc.targetModel ? [model.name, assoc.targetModel] : [assoc.targetModel, model.name];
3036
3182
  const junctionModule = toSnakeCase(`${leftName}To${rightName}`);
@@ -3060,15 +3206,21 @@ function generateEntityFile(model, allModels, enums, serde = {}) {
3060
3206
  const scalarFields = model.fields.filter((f) => f.kind !== "object");
3061
3207
  const fieldLines = [];
3062
3208
  for (const field of scalarFields) {
3063
- const attrs = buildSeaOrmAttributes(field, field.isId || compositePkFieldNames.has(field.name), isCompositePk);
3064
- const rustType = enumNames.has(field.type) ? field.isRequired ? field.type : `Option<${field.type}>` : prismaTypeToRustType(field.type, field.isRequired);
3065
- 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);
3066
3214
  for (const attr of attrs) fieldLines.push(` ${attr}`);
3067
3215
  fieldLines.push(` pub ${fieldName}: ${rustType},`);
3068
3216
  }
3069
3217
  const relationEnum = generateRelationEnum(model, associations);
3070
3218
  const relatedImpls = generateRelatedImpls(model, associations);
3071
- 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
+ ];
3072
3224
  const deriveModel = canDeriveEq(scalarFields) ? "#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]" : "#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]";
3073
3225
  const serdeAttrs = buildSerdeAttributes(serde);
3074
3226
  return [
@@ -3224,7 +3376,7 @@ const PRISMA_TO_PYTHON = {
3224
3376
  Decimal: "Decimal",
3225
3377
  Boolean: "bool",
3226
3378
  DateTime: "datetime",
3227
- Json: "dict",
3379
+ Json: "dict[str, Any]",
3228
3380
  Bytes: "bytes"
3229
3381
  };
3230
3382
  const PRISMA_TO_SQLALCHEMY = {
@@ -3244,6 +3396,46 @@ function prismaTypeToSQLAlchemyType(type) {
3244
3396
  function prismaTypeToPythonType(type) {
3245
3397
  return PRISMA_TO_PYTHON[type] ?? "str";
3246
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
+ }
3247
3439
  function resolveNativeType(field) {
3248
3440
  const baseType = prismaTypeToSQLAlchemyType(field.type);
3249
3441
  if (!field.nativeType) return baseType;
@@ -3402,16 +3594,18 @@ function isAutoincrement(field) {
3402
3594
  function needsForeignKeysParam(targetModel, assocs) {
3403
3595
  return assocs.filter((a) => a.targetModel === targetModel).length > 1;
3404
3596
  }
3405
- function findBackPopulates(targetModelName, sourceModelName, foreignKey, allModels) {
3597
+ function findBackPopulates(targetModelName, sourceModelName, foreignKey, allModels, sourceFieldName) {
3406
3598
  const targetModel = allModels.find((m) => m.name === targetModelName);
3599
+ const sourceModel = allModels.find((m) => m.name === sourceModelName);
3407
3600
  if (!targetModel) return makeSnakeCase(sourceModelName);
3601
+ const relationName = (sourceModel?.fields.find((f) => f.kind === "object" && f.name === sourceFieldName))?.relationName;
3408
3602
  const backField = targetModel.fields.find((f) => {
3409
3603
  if (f.kind !== "object") return false;
3410
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;
3411
3607
  if (f.relationFromFields?.includes(foreignKey)) return true;
3412
- const sourceModel = allModels.find((m) => m.name === sourceModelName);
3413
- if (!sourceModel) return false;
3414
- 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;
3415
3609
  });
3416
3610
  return backField ? makeSnakeCase(backField.name) : makeSnakeCase(sourceModelName);
3417
3611
  }
@@ -3422,15 +3616,19 @@ function findM2MBackPopulates(targetModelName, sourceModelName, relationName, al
3422
3616
  return backField ? makeSnakeCase(backField.name) : makeSnakeCase(sourceModelName);
3423
3617
  }
3424
3618
  function generateColumn(field, isPk, isFk, associations, allModels, enumMap) {
3425
- const snakeName = field.dbName ?? makeSnakeCase(field.name);
3426
- const pythonType = field.kind === "enum" ? "str" : pythonTypeForNative(field);
3427
- 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}]`;
3428
3624
  const colArgs = [];
3625
+ if (attrName !== columnName) colArgs.push(`"${columnName}"`);
3429
3626
  if (field.kind === "enum") {
3430
3627
  const values = enumMap.get(field.type);
3431
- const valuesStr = values ? values.map((v) => `"${v}"`).join(", ") : "";
3432
- colArgs.push(`Enum(${valuesStr}, name="${makeSnakeCase(field.type)}")`);
3433
- } 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));
3434
3632
  if (isFk) {
3435
3633
  const assoc = associations.belongsTo.find((a) => a.foreignKey === field.name);
3436
3634
  if (assoc) {
@@ -3448,8 +3646,8 @@ function generateColumn(field, isPk, isFk, associations, allModels, enumMap) {
3448
3646
  if (defaultVal !== null && !isPk) colArgs.push(`default=${defaultVal}`);
3449
3647
  }
3450
3648
  if (field.isUpdatedAt) colArgs.push("onupdate=func.now()");
3451
- if (colArgs.length === 0) return ` ${snakeName}: Mapped[${typeHint}]`;
3452
- 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(", ")})`;
3453
3651
  }
3454
3652
  function generateTableArgs(model, indexes) {
3455
3653
  const uniqueConstraints = model.uniqueFields.map((fields) => {
@@ -3475,15 +3673,17 @@ function generateBelongsToRelationships(associations, model, allModels) {
3475
3673
  return associations.belongsTo.map((assoc) => {
3476
3674
  const snakeName = makeSnakeCase(assoc.name);
3477
3675
  const snakeFk = model.fields.find((f) => f.name === assoc.foreignKey)?.dbName ?? makeSnakeCase(assoc.foreignKey);
3478
- const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels);
3676
+ const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels, assoc.name);
3479
3677
  const fkClause = needsForeignKeysParam(assoc.targetModel, associations.belongsTo) ? `foreign_keys=[${snakeFk}], ` : "";
3480
- 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}")`;
3481
3681
  });
3482
3682
  }
3483
3683
  function generateHasManyRelationships(associations, model, allModels) {
3484
3684
  return associations.hasMany.map((assoc) => {
3485
3685
  const snakeName = makeSnakeCase(assoc.name);
3486
- const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels);
3686
+ const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels, assoc.name);
3487
3687
  const targetModel = allModels.find((m) => m.name === assoc.targetModel);
3488
3688
  const needsFkParam = targetModel ? needsForeignKeysParam(model.name, getAssociations(targetModel, allModels).belongsTo) : false;
3489
3689
  const targetFkSnake = makeSnakeCase(assoc.foreignKey);
@@ -3494,7 +3694,7 @@ function generateHasManyRelationships(associations, model, allModels) {
3494
3694
  function generateHasOneRelationships(associations, model, allModels) {
3495
3695
  return associations.hasOne.map((assoc) => {
3496
3696
  const snakeName = makeSnakeCase(assoc.name);
3497
- const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels);
3697
+ const backPop = findBackPopulates(assoc.targetModel, model.name, assoc.foreignKey, allModels, assoc.name);
3498
3698
  const targetModel = allModels.find((m) => m.name === assoc.targetModel);
3499
3699
  const needsFkParam = targetModel ? needsForeignKeysParam(model.name, getAssociations(targetModel, allModels).belongsTo) : false;
3500
3700
  const targetFkSnake = makeSnakeCase(assoc.foreignKey);
@@ -3546,6 +3746,8 @@ function collectGlobalImports(models, _enums, indexes, m2mTables) {
3546
3746
  const needsOptional = models.some((m) => m.fields.some((f) => f.kind !== "object" && !f.isRequired));
3547
3747
  const hasRelationship = models.some((m) => m.fields.some((f) => f.kind === "object"));
3548
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));
3549
3751
  const needsDecimal = models.some((m) => m.fields.some((f) => f.type === "Decimal"));
3550
3752
  const needsDatetime = models.some((m) => m.fields.some((f) => f.type === "DateTime"));
3551
3753
  const needsUuid = models.some((m) => m.fields.some((f) => {
@@ -3570,6 +3772,7 @@ function collectGlobalImports(models, _enums, indexes, m2mTables) {
3570
3772
  saImports.add("Enum");
3571
3773
  continue;
3572
3774
  }
3775
+ if (field.isList) saImports.add(prismaTypeToSQLAlchemyType(field.type));
3573
3776
  if (needsExplicitSaType(field)) {
3574
3777
  const resolved = resolveNativeType(field);
3575
3778
  saImports.add(resolved.replace(/\(.*\)$/, ""));
@@ -3595,6 +3798,7 @@ function collectGlobalImports(models, _enums, indexes, m2mTables) {
3595
3798
  }
3596
3799
  }
3597
3800
  if (needsFunc) saImports.add("func");
3801
+ if (needsArray) saImports.add("ARRAY");
3598
3802
  const lines = [];
3599
3803
  const sortedSa = [...saImports].sort();
3600
3804
  if (sortedSa.length > 0) lines.push(`from sqlalchemy import ${sortedSa.join(", ")}`);
@@ -3605,7 +3809,8 @@ function collectGlobalImports(models, _enums, indexes, m2mTables) {
3605
3809
  ];
3606
3810
  if (hasRelationship) ormImports.push("relationship");
3607
3811
  lines.push(`from sqlalchemy.orm import ${ormImports.sort().join(", ")}`);
3608
- 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(", ")}`);
3609
3814
  if (needsDecimal) lines.push("from decimal import Decimal as DecimalType");
3610
3815
  const dtParts = [];
3611
3816
  if (needsDatetime) dtParts.push("datetime");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hekireki",
3
- "version": "0.8.5",
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,41 +51,39 @@
51
51
  "dist"
52
52
  ],
53
53
  "type": "module",
54
- "publishConfig": {
55
- "access": "public"
56
- },
57
54
  "dependencies": {
58
- "@hono/node-server": "^2.0.4",
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
- "hono": "^4.12.25",
63
- "oxfmt": "^0.54.0"
59
+ "hono": "^4.12.27",
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": "^25.9.3",
70
- "@typescript/native-preview": "7.0.0-dev.20260611.2",
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.0",
73
- "better-auth": "^1.6.17",
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
- "effect": "^3.21.3",
74
+ "effect": "^3.21.4",
77
75
  "json-schema-to-ts": "^3.1.1",
78
76
  "prisma": "^7.8.0",
79
- "tsdown": "^0.22.2",
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
  }