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 +20 -0
- package/dist/bin/ajv.js +1 -1
- package/dist/bin/arktype.js +1 -1
- package/dist/bin/dbml.js +1 -1
- package/dist/bin/docs.js +1 -1
- package/dist/bin/drizzle.js +1 -1
- package/dist/bin/ecto.js +1 -1
- package/dist/bin/effect.js +1 -1
- package/dist/bin/gorm.js +1 -1
- package/dist/bin/mermaid-er.js +1 -1
- package/dist/bin/sea-orm.js +1 -1
- package/dist/bin/sqlalchemy.js +1 -1
- package/dist/bin/typebox.js +1 -1
- package/dist/bin/valibot.js +1 -1
- package/dist/bin/zod.js +1 -1
- package/dist/{bin-Dcg9ZO7c.js → bin-Dh-MFTeu.js} +297 -92
- package/package.json +13 -15
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
package/dist/bin/arktype.js
CHANGED
package/dist/bin/dbml.js
CHANGED
package/dist/bin/docs.js
CHANGED
package/dist/bin/drizzle.js
CHANGED
package/dist/bin/ecto.js
CHANGED
package/dist/bin/effect.js
CHANGED
package/dist/bin/gorm.js
CHANGED
package/dist/bin/mermaid-er.js
CHANGED
package/dist/bin/sea-orm.js
CHANGED
package/dist/bin/sqlalchemy.js
CHANGED
package/dist/bin/typebox.js
CHANGED
package/dist/bin/valibot.js
CHANGED
package/dist/bin/zod.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
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,
|
|
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
|
-
|
|
1835
|
-
needsCuid: false
|
|
1864
|
+
imports: []
|
|
1836
1865
|
};
|
|
1837
1866
|
if (isFieldDefault(dflt)) switch (dflt.name) {
|
|
1838
1867
|
case "autoincrement": return {
|
|
1839
1868
|
chain: "",
|
|
1840
|
-
|
|
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
|
-
|
|
1847
|
-
needsCuid: false
|
|
1874
|
+
imports: [SQL_IMPORT]
|
|
1848
1875
|
};
|
|
1849
1876
|
if (provider === "mysql") return {
|
|
1850
1877
|
chain: ".default(sql`CURRENT_TIMESTAMP(3)`)",
|
|
1851
|
-
|
|
1852
|
-
needsCuid: false
|
|
1878
|
+
imports: [SQL_IMPORT]
|
|
1853
1879
|
};
|
|
1854
1880
|
return {
|
|
1855
1881
|
chain: ".defaultNow()",
|
|
1856
|
-
|
|
1857
|
-
needsCuid: false
|
|
1882
|
+
imports: []
|
|
1858
1883
|
};
|
|
1859
1884
|
case "uuid": return {
|
|
1860
1885
|
chain: ".$defaultFn(() => crypto.randomUUID())",
|
|
1861
|
-
|
|
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
|
-
|
|
1867
|
-
|
|
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
|
-
|
|
1873
|
-
needsCuid: false
|
|
1922
|
+
imports: [SQL_IMPORT]
|
|
1874
1923
|
};
|
|
1875
1924
|
return {
|
|
1876
1925
|
chain: "",
|
|
1877
|
-
|
|
1878
|
-
needsCuid: false
|
|
1926
|
+
imports: []
|
|
1879
1927
|
};
|
|
1880
1928
|
default: return {
|
|
1881
1929
|
chain: "",
|
|
1882
|
-
|
|
1883
|
-
needsCuid: false
|
|
1930
|
+
imports: []
|
|
1884
1931
|
};
|
|
1885
1932
|
}
|
|
1886
1933
|
if (typeof dflt === "string") return {
|
|
1887
1934
|
chain: `.default('${dflt}')`,
|
|
1888
|
-
|
|
1889
|
-
needsCuid: false
|
|
1935
|
+
imports: []
|
|
1890
1936
|
};
|
|
1891
1937
|
if (typeof dflt === "number") return {
|
|
1892
1938
|
chain: fieldType === "Decimal" ? `.default('${dflt}')` : `.default(${dflt})`,
|
|
1893
|
-
|
|
1894
|
-
needsCuid: false
|
|
1939
|
+
imports: []
|
|
1895
1940
|
};
|
|
1896
1941
|
if (typeof dflt === "boolean") return {
|
|
1897
1942
|
chain: `.default(${dflt})`,
|
|
1898
|
-
|
|
1899
|
-
needsCuid: false
|
|
1943
|
+
imports: []
|
|
1900
1944
|
};
|
|
1901
1945
|
return {
|
|
1902
1946
|
chain: "",
|
|
1903
|
-
|
|
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
|
-
|
|
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)
|
|
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"
|
|
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
|
|
2624
|
+
const cols = fields.map((f) => {
|
|
2563
2625
|
return model.fields.find((mf) => mf.name === f)?.dbName ?? makeSnakeCase(f);
|
|
2564
|
-
})
|
|
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
|
|
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
|
-
|
|
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)}
|
|
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
|
-
|
|
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() +
|
|
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
|
|
3140
|
+
const emittedTargets = /* @__PURE__ */ new Set();
|
|
3000
3141
|
for (const assoc of associations.belongsTo) {
|
|
3001
|
-
if (
|
|
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 (
|
|
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 (
|
|
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
|
|
3064
|
-
const
|
|
3065
|
-
const
|
|
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 = [
|
|
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
|
-
|
|
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
|
|
3426
|
-
const
|
|
3427
|
-
const
|
|
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
|
|
3432
|
-
colArgs.push(`
|
|
3433
|
-
} else if (
|
|
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 ` ${
|
|
3452
|
-
return ` ${
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
63
|
-
"oxfmt": "^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": "^
|
|
70
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
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.
|
|
73
|
-
"better-auth": "^1.6.
|
|
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.
|
|
74
|
+
"effect": "^3.21.4",
|
|
77
75
|
"json-schema-to-ts": "^3.1.1",
|
|
78
76
|
"prisma": "^7.8.0",
|
|
79
|
-
"tsdown": "^0.22.
|
|
77
|
+
"tsdown": "^0.22.3",
|
|
80
78
|
"tsx": "^4.22.4",
|
|
81
79
|
"typescript": "^6.0.3",
|
|
82
|
-
"valibot": "1.4.
|
|
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
|
-
"
|
|
87
|
+
"conformance": "pnpm build && bash conformance/check.sh"
|
|
90
88
|
}
|
|
91
89
|
}
|