arkormx 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +252 -12
- package/dist/index.cjs +252 -12
- package/dist/index.d.cts +28 -0
- package/dist/index.d.mts +28 -0
- package/dist/index.mjs +252 -12
- package/package.json +4 -4
package/dist/cli.mjs
CHANGED
|
@@ -1870,10 +1870,225 @@ var CliApp = class {
|
|
|
1870
1870
|
if (value === "String") return "string";
|
|
1871
1871
|
if (value === "Boolean") return "boolean";
|
|
1872
1872
|
if (value === "DateTime") return "Date";
|
|
1873
|
-
if (value === "Json") return "Record<string, unknown>";
|
|
1873
|
+
if (value === "Json") return "Record<string, unknown> | unknown[]";
|
|
1874
1874
|
if (value === "Bytes") return "Buffer";
|
|
1875
1875
|
return "unknown";
|
|
1876
1876
|
}
|
|
1877
|
+
splitTopLevel(value, delimiter) {
|
|
1878
|
+
const parts = [];
|
|
1879
|
+
let start = 0;
|
|
1880
|
+
let angleDepth = 0;
|
|
1881
|
+
let parenthesisDepth = 0;
|
|
1882
|
+
let quote = null;
|
|
1883
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1884
|
+
const character = value[index];
|
|
1885
|
+
const previous = index > 0 ? value[index - 1] : "";
|
|
1886
|
+
if (quote) {
|
|
1887
|
+
if (character === quote && previous !== "\\") quote = null;
|
|
1888
|
+
continue;
|
|
1889
|
+
}
|
|
1890
|
+
if (character === "'" || character === "\"") {
|
|
1891
|
+
quote = character;
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
if (character === "<") {
|
|
1895
|
+
angleDepth += 1;
|
|
1896
|
+
continue;
|
|
1897
|
+
}
|
|
1898
|
+
if (character === ">") {
|
|
1899
|
+
angleDepth = Math.max(0, angleDepth - 1);
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
if (character === "(") {
|
|
1903
|
+
parenthesisDepth += 1;
|
|
1904
|
+
continue;
|
|
1905
|
+
}
|
|
1906
|
+
if (character === ")") {
|
|
1907
|
+
parenthesisDepth = Math.max(0, parenthesisDepth - 1);
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
if (character === delimiter && angleDepth === 0 && parenthesisDepth === 0) {
|
|
1911
|
+
parts.push(value.slice(start, index).trim());
|
|
1912
|
+
start = index + 1;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
parts.push(value.slice(start).trim());
|
|
1916
|
+
return parts.filter(Boolean);
|
|
1917
|
+
}
|
|
1918
|
+
hasWrappedParentheses(value) {
|
|
1919
|
+
if (!value.startsWith("(") || !value.endsWith(")")) return false;
|
|
1920
|
+
let depth = 0;
|
|
1921
|
+
let quote = null;
|
|
1922
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1923
|
+
const character = value[index];
|
|
1924
|
+
const previous = index > 0 ? value[index - 1] : "";
|
|
1925
|
+
if (quote) {
|
|
1926
|
+
if (character === quote && previous !== "\\") quote = null;
|
|
1927
|
+
continue;
|
|
1928
|
+
}
|
|
1929
|
+
if (character === "'" || character === "\"") {
|
|
1930
|
+
quote = character;
|
|
1931
|
+
continue;
|
|
1932
|
+
}
|
|
1933
|
+
if (character === "(") depth += 1;
|
|
1934
|
+
if (character === ")") {
|
|
1935
|
+
depth -= 1;
|
|
1936
|
+
if (depth === 0 && index < value.length - 1) return false;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
return depth === 0;
|
|
1940
|
+
}
|
|
1941
|
+
stripWrappedParentheses(value) {
|
|
1942
|
+
let nextValue = value.trim();
|
|
1943
|
+
while (this.hasWrappedParentheses(nextValue)) nextValue = nextValue.slice(1, -1).trim();
|
|
1944
|
+
return nextValue;
|
|
1945
|
+
}
|
|
1946
|
+
parseDeclarationType(value) {
|
|
1947
|
+
const trimmed = this.stripWrappedParentheses(value.trim());
|
|
1948
|
+
if (!trimmed) return null;
|
|
1949
|
+
const unionParts = this.splitTopLevel(trimmed, "|");
|
|
1950
|
+
if (unionParts.length > 1) {
|
|
1951
|
+
const types = unionParts.map((part) => this.parseDeclarationType(part)).filter((part) => part !== null);
|
|
1952
|
+
if (types.length !== unionParts.length) return null;
|
|
1953
|
+
return {
|
|
1954
|
+
kind: "union",
|
|
1955
|
+
types: types.flatMap((type) => type.kind === "union" ? type.types : [type])
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
if (trimmed.endsWith("[]")) {
|
|
1959
|
+
const element = this.parseDeclarationType(trimmed.slice(0, -2));
|
|
1960
|
+
if (!element) return null;
|
|
1961
|
+
return {
|
|
1962
|
+
kind: "array",
|
|
1963
|
+
element
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
const arrayMatch = trimmed.match(/^(Array|ReadonlyArray)<([\s\S]+)>$/);
|
|
1967
|
+
if (arrayMatch) {
|
|
1968
|
+
const element = this.parseDeclarationType(arrayMatch[2]);
|
|
1969
|
+
if (!element) return null;
|
|
1970
|
+
return {
|
|
1971
|
+
kind: "array",
|
|
1972
|
+
element
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
if (trimmed === "null") return { kind: "null" };
|
|
1976
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith("\"") && trimmed.endsWith("\"")) return {
|
|
1977
|
+
kind: "string-literal",
|
|
1978
|
+
value: trimmed.slice(1, -1)
|
|
1979
|
+
};
|
|
1980
|
+
if (trimmed === "Record<string, unknown>") return {
|
|
1981
|
+
kind: "named",
|
|
1982
|
+
name: trimmed
|
|
1983
|
+
};
|
|
1984
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(trimmed)) return {
|
|
1985
|
+
kind: "named",
|
|
1986
|
+
name: trimmed
|
|
1987
|
+
};
|
|
1988
|
+
return null;
|
|
1989
|
+
}
|
|
1990
|
+
expandUnion(node) {
|
|
1991
|
+
return node.kind === "union" ? node.types : [node];
|
|
1992
|
+
}
|
|
1993
|
+
isEnumTypeName(value, enums) {
|
|
1994
|
+
return enums.has(value);
|
|
1995
|
+
}
|
|
1996
|
+
isDeclarationNodeAssignable(actual, expected, enums) {
|
|
1997
|
+
if (expected.kind === "named") {
|
|
1998
|
+
if (expected.name === "unknown") return true;
|
|
1999
|
+
if (actual.kind === "named") {
|
|
2000
|
+
if (actual.name === expected.name) return true;
|
|
2001
|
+
if (expected.name === "string" && this.isEnumTypeName(actual.name, enums)) return true;
|
|
2002
|
+
}
|
|
2003
|
+
if (actual.kind === "string-literal") {
|
|
2004
|
+
if (expected.name === "string") return true;
|
|
2005
|
+
if (enums.get(expected.name)?.includes(actual.value)) return true;
|
|
2006
|
+
}
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
if (expected.kind === "array") return actual.kind === "array" && this.isDeclarationAssignable(actual.element, expected.element, enums);
|
|
2010
|
+
if (expected.kind === "null") return actual.kind === "null";
|
|
2011
|
+
if (expected.kind === "string-literal") return actual.kind === "string-literal" && actual.value === expected.value;
|
|
2012
|
+
return false;
|
|
2013
|
+
}
|
|
2014
|
+
isDeclarationAssignable(actual, expected, enums) {
|
|
2015
|
+
const actualTypes = this.expandUnion(actual);
|
|
2016
|
+
const expectedTypes = this.expandUnion(expected);
|
|
2017
|
+
return actualTypes.every((actualType) => {
|
|
2018
|
+
return expectedTypes.some((expectedType) => {
|
|
2019
|
+
return this.isDeclarationNodeAssignable(actualType, expectedType, enums);
|
|
2020
|
+
});
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
isCompatibleDeclarationType(actualType, expectedType, enums) {
|
|
2024
|
+
const actual = this.parseDeclarationType(actualType);
|
|
2025
|
+
const expected = this.parseDeclarationType(expectedType);
|
|
2026
|
+
if (!actual || !expected) return actualType.replace(/\s+/g, " ").trim() === expectedType.replace(/\s+/g, " ").trim();
|
|
2027
|
+
return this.isDeclarationAssignable(actual, expected, enums);
|
|
2028
|
+
}
|
|
2029
|
+
collectEnumReferencesFromNode(node, enums, collected) {
|
|
2030
|
+
if (node.kind === "named" && enums.has(node.name)) {
|
|
2031
|
+
collected.add(node.name);
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
if (node.kind === "array") {
|
|
2035
|
+
this.collectEnumReferencesFromNode(node.element, enums, collected);
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
if (node.kind === "union") node.types.forEach((type) => this.collectEnumReferencesFromNode(type, enums, collected));
|
|
2039
|
+
}
|
|
2040
|
+
collectEnumReferences(type, enums) {
|
|
2041
|
+
const parsed = this.parseDeclarationType(type);
|
|
2042
|
+
if (!parsed) return [];
|
|
2043
|
+
const collected = /* @__PURE__ */ new Set();
|
|
2044
|
+
this.collectEnumReferencesFromNode(parsed, enums, collected);
|
|
2045
|
+
return [...collected].sort((left, right) => left.localeCompare(right));
|
|
2046
|
+
}
|
|
2047
|
+
syncPrismaEnumImports(modelSource, enumTypes) {
|
|
2048
|
+
if (enumTypes.length === 0) return modelSource;
|
|
2049
|
+
const importRegex = /^import\s+type\s+\{([^}]+)\}\s+from\s+['"]@prisma\/client['"]\s*;?$/m;
|
|
2050
|
+
const existingImport = modelSource.match(importRegex);
|
|
2051
|
+
if (existingImport) {
|
|
2052
|
+
const existingTypes = existingImport[1].split(",").map((value) => value.trim()).filter(Boolean);
|
|
2053
|
+
const mergedTypes = [...new Set([...existingTypes, ...enumTypes])].sort((left, right) => left.localeCompare(right));
|
|
2054
|
+
return modelSource.replace(importRegex, `import type { ${mergedTypes.join(", ")} } from '@prisma/client'`);
|
|
2055
|
+
}
|
|
2056
|
+
const lines = modelSource.split("\n");
|
|
2057
|
+
let insertionIndex = 0;
|
|
2058
|
+
while (insertionIndex < lines.length && lines[insertionIndex].trim().startsWith("import ")) insertionIndex += 1;
|
|
2059
|
+
lines.splice(insertionIndex, 0, `import type { ${enumTypes.join(", ")} } from '@prisma/client'`);
|
|
2060
|
+
return lines.join("\n");
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Parse Prisma enum definitions from a schema and return their member names.
|
|
2064
|
+
*
|
|
2065
|
+
* @param schema The Prisma schema source.
|
|
2066
|
+
* @returns A map of enum names to their declared member names.
|
|
2067
|
+
*/
|
|
2068
|
+
parsePrismaEnums(schema) {
|
|
2069
|
+
const enums = /* @__PURE__ */ new Map();
|
|
2070
|
+
for (const match of schema.matchAll(PRISMA_ENUM_REGEX)) {
|
|
2071
|
+
const enumName = match[1];
|
|
2072
|
+
const values = match[0].split("\n").slice(1, -1).map((line) => line.trim()).filter((line) => Boolean(line) && !line.startsWith("//")).map((line) => {
|
|
2073
|
+
return line.match(/^([A-Za-z][A-Za-z0-9_]*)\b/)?.[1];
|
|
2074
|
+
}).filter((value) => Boolean(value));
|
|
2075
|
+
enums.set(enumName, values);
|
|
2076
|
+
}
|
|
2077
|
+
return enums;
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Resolve the generated TypeScript declaration type for a Prisma field.
|
|
2081
|
+
*
|
|
2082
|
+
* @param fieldType The Prisma field type token.
|
|
2083
|
+
* @param isList Whether the field is declared as a Prisma list.
|
|
2084
|
+
* @param enums Known Prisma enum definitions.
|
|
2085
|
+
* @returns The declaration type to emit, or null when unsupported.
|
|
2086
|
+
*/
|
|
2087
|
+
prismaFieldTypeToTs(fieldType, isList, enums) {
|
|
2088
|
+
const baseType = enums.has(fieldType) ? fieldType : this.prismaTypeToTs(fieldType);
|
|
2089
|
+
if (baseType === "unknown" && !enums.has(fieldType)) return null;
|
|
2090
|
+
return isList ? `Array<${baseType}>` : baseType;
|
|
2091
|
+
}
|
|
1877
2092
|
/**
|
|
1878
2093
|
* Parse the Prisma schema to extract model definitions and their fields, focusing
|
|
1879
2094
|
* on scalar types.
|
|
@@ -1883,6 +2098,7 @@ var CliApp = class {
|
|
|
1883
2098
|
*/
|
|
1884
2099
|
parsePrismaModels(schema) {
|
|
1885
2100
|
const models = [];
|
|
2101
|
+
const enumDefinitions = this.parsePrismaEnums(schema);
|
|
1886
2102
|
const modelRegex = /model\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
|
|
1887
2103
|
const scalarTypes = new Set([
|
|
1888
2104
|
"Int",
|
|
@@ -1903,14 +2119,16 @@ var CliApp = class {
|
|
|
1903
2119
|
body.split("\n").forEach((rawLine) => {
|
|
1904
2120
|
const line = rawLine.trim();
|
|
1905
2121
|
if (!line || line.startsWith("@@") || line.startsWith("//")) return;
|
|
1906
|
-
const fieldMatch = line.match(/^(\w+)\s+([A-Za-z]
|
|
2122
|
+
const fieldMatch = line.match(/^(\w+)\s+([A-Za-z][A-Za-z0-9_]*)(\[\])?(\?)?(?:\s|$)/);
|
|
1907
2123
|
if (!fieldMatch) return;
|
|
1908
2124
|
const fieldType = fieldMatch[2];
|
|
1909
|
-
if (!scalarTypes.has(fieldType)) return;
|
|
2125
|
+
if (!scalarTypes.has(fieldType) && !enumDefinitions.has(fieldType)) return;
|
|
2126
|
+
const declarationType = this.prismaFieldTypeToTs(fieldType, Boolean(fieldMatch[3]), enumDefinitions);
|
|
2127
|
+
if (!declarationType) return;
|
|
1910
2128
|
fields.push({
|
|
1911
2129
|
name: fieldMatch[1],
|
|
1912
|
-
type:
|
|
1913
|
-
nullable: Boolean(fieldMatch[
|
|
2130
|
+
type: declarationType,
|
|
2131
|
+
nullable: Boolean(fieldMatch[4])
|
|
1914
2132
|
});
|
|
1915
2133
|
});
|
|
1916
2134
|
models.push({
|
|
@@ -1931,7 +2149,7 @@ var CliApp = class {
|
|
|
1931
2149
|
* @param declarations A list of attribute declarations to sync.
|
|
1932
2150
|
* @returns An object containing the updated content and a flag indicating if it was updated.
|
|
1933
2151
|
*/
|
|
1934
|
-
syncModelDeclarations(modelSource, declarations) {
|
|
2152
|
+
syncModelDeclarations(modelSource, declarations, enums) {
|
|
1935
2153
|
const lines = modelSource.split("\n");
|
|
1936
2154
|
const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model<.+>\s*\{/.test(line));
|
|
1937
2155
|
if (classIndex < 0) return {
|
|
@@ -1953,16 +2171,38 @@ var CliApp = class {
|
|
|
1953
2171
|
content: modelSource,
|
|
1954
2172
|
updated: false
|
|
1955
2173
|
};
|
|
1956
|
-
const
|
|
1957
|
-
const
|
|
2174
|
+
const withinClass = lines.slice(classIndex + 1, classEndIndex);
|
|
2175
|
+
const existingDeclarations = /* @__PURE__ */ new Map();
|
|
2176
|
+
withinClass.forEach((line) => {
|
|
2177
|
+
const declarationMatch = line.match(/^\s*declare\s+(\w+)\??:\s*([^;\n]+);?\s*$/);
|
|
2178
|
+
if (!declarationMatch) return;
|
|
2179
|
+
existingDeclarations.set(declarationMatch[1], {
|
|
2180
|
+
name: declarationMatch[1],
|
|
2181
|
+
raw: line.trim(),
|
|
2182
|
+
type: declarationMatch[2].trim()
|
|
2183
|
+
});
|
|
2184
|
+
});
|
|
2185
|
+
const withoutDeclares = withinClass.filter((line) => !/^\s*declare\s+\w+\??:\s*[^\n]+$/.test(line));
|
|
2186
|
+
const chosenDeclarations = declarations.map((declaration) => {
|
|
2187
|
+
const expectedType = `${declaration.type}${declaration.nullable ? " | null" : ""}`;
|
|
2188
|
+
const existingDeclaration = existingDeclarations.get(declaration.name);
|
|
2189
|
+
if (existingDeclaration && this.isCompatibleDeclarationType(existingDeclaration.type, expectedType, enums)) return existingDeclaration.raw;
|
|
2190
|
+
return `declare ${declaration.name}: ${expectedType}`;
|
|
2191
|
+
});
|
|
2192
|
+
const rebuiltClass = [...chosenDeclarations.map((declaration) => ` ${declaration}`), ...withoutDeclares];
|
|
1958
2193
|
const content = [
|
|
1959
2194
|
...lines.slice(0, classIndex + 1),
|
|
1960
2195
|
...rebuiltClass,
|
|
1961
2196
|
...lines.slice(classEndIndex)
|
|
1962
2197
|
].join("\n");
|
|
2198
|
+
const enumImports = [...new Set(chosenDeclarations.flatMap((declaration) => {
|
|
2199
|
+
const type = declaration.replace(/^declare\s+\w+\??:\s*/, "").replace(/;$/, "").trim();
|
|
2200
|
+
return this.collectEnumReferences(type, enums);
|
|
2201
|
+
}))].sort((left, right) => left.localeCompare(right));
|
|
2202
|
+
const contentWithImports = this.syncPrismaEnumImports(content, enumImports);
|
|
1963
2203
|
return {
|
|
1964
|
-
content,
|
|
1965
|
-
updated:
|
|
2204
|
+
content: contentWithImports,
|
|
2205
|
+
updated: contentWithImports !== modelSource
|
|
1966
2206
|
};
|
|
1967
2207
|
}
|
|
1968
2208
|
/**
|
|
@@ -1982,6 +2222,7 @@ var CliApp = class {
|
|
|
1982
2222
|
if (!existsSync$1(schemaPath)) throw new Error(`Prisma schema file not found: ${schemaPath}`);
|
|
1983
2223
|
if (!existsSync$1(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
|
|
1984
2224
|
const schema = readFileSync$1(schemaPath, "utf-8");
|
|
2225
|
+
const prismaEnums = this.parsePrismaEnums(schema);
|
|
1985
2226
|
const prismaModels = this.parsePrismaModels(schema);
|
|
1986
2227
|
const modelFiles = readdirSync$1(modelsDir).filter((file) => file.endsWith(".ts"));
|
|
1987
2228
|
const updated = [];
|
|
@@ -2001,8 +2242,7 @@ var CliApp = class {
|
|
|
2001
2242
|
skipped.push(filePath);
|
|
2002
2243
|
return;
|
|
2003
2244
|
}
|
|
2004
|
-
const
|
|
2005
|
-
const synced = this.syncModelDeclarations(source, declarations);
|
|
2245
|
+
const synced = this.syncModelDeclarations(source, prismaModel.fields, prismaEnums);
|
|
2006
2246
|
if (!synced.updated) {
|
|
2007
2247
|
skipped.push(filePath);
|
|
2008
2248
|
return;
|
package/dist/index.cjs
CHANGED
|
@@ -2146,10 +2146,225 @@ var CliApp = class {
|
|
|
2146
2146
|
if (value === "String") return "string";
|
|
2147
2147
|
if (value === "Boolean") return "boolean";
|
|
2148
2148
|
if (value === "DateTime") return "Date";
|
|
2149
|
-
if (value === "Json") return "Record<string, unknown>";
|
|
2149
|
+
if (value === "Json") return "Record<string, unknown> | unknown[]";
|
|
2150
2150
|
if (value === "Bytes") return "Buffer";
|
|
2151
2151
|
return "unknown";
|
|
2152
2152
|
}
|
|
2153
|
+
splitTopLevel(value, delimiter) {
|
|
2154
|
+
const parts = [];
|
|
2155
|
+
let start = 0;
|
|
2156
|
+
let angleDepth = 0;
|
|
2157
|
+
let parenthesisDepth = 0;
|
|
2158
|
+
let quote = null;
|
|
2159
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2160
|
+
const character = value[index];
|
|
2161
|
+
const previous = index > 0 ? value[index - 1] : "";
|
|
2162
|
+
if (quote) {
|
|
2163
|
+
if (character === quote && previous !== "\\") quote = null;
|
|
2164
|
+
continue;
|
|
2165
|
+
}
|
|
2166
|
+
if (character === "'" || character === "\"") {
|
|
2167
|
+
quote = character;
|
|
2168
|
+
continue;
|
|
2169
|
+
}
|
|
2170
|
+
if (character === "<") {
|
|
2171
|
+
angleDepth += 1;
|
|
2172
|
+
continue;
|
|
2173
|
+
}
|
|
2174
|
+
if (character === ">") {
|
|
2175
|
+
angleDepth = Math.max(0, angleDepth - 1);
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
if (character === "(") {
|
|
2179
|
+
parenthesisDepth += 1;
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
if (character === ")") {
|
|
2183
|
+
parenthesisDepth = Math.max(0, parenthesisDepth - 1);
|
|
2184
|
+
continue;
|
|
2185
|
+
}
|
|
2186
|
+
if (character === delimiter && angleDepth === 0 && parenthesisDepth === 0) {
|
|
2187
|
+
parts.push(value.slice(start, index).trim());
|
|
2188
|
+
start = index + 1;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
parts.push(value.slice(start).trim());
|
|
2192
|
+
return parts.filter(Boolean);
|
|
2193
|
+
}
|
|
2194
|
+
hasWrappedParentheses(value) {
|
|
2195
|
+
if (!value.startsWith("(") || !value.endsWith(")")) return false;
|
|
2196
|
+
let depth = 0;
|
|
2197
|
+
let quote = null;
|
|
2198
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2199
|
+
const character = value[index];
|
|
2200
|
+
const previous = index > 0 ? value[index - 1] : "";
|
|
2201
|
+
if (quote) {
|
|
2202
|
+
if (character === quote && previous !== "\\") quote = null;
|
|
2203
|
+
continue;
|
|
2204
|
+
}
|
|
2205
|
+
if (character === "'" || character === "\"") {
|
|
2206
|
+
quote = character;
|
|
2207
|
+
continue;
|
|
2208
|
+
}
|
|
2209
|
+
if (character === "(") depth += 1;
|
|
2210
|
+
if (character === ")") {
|
|
2211
|
+
depth -= 1;
|
|
2212
|
+
if (depth === 0 && index < value.length - 1) return false;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
return depth === 0;
|
|
2216
|
+
}
|
|
2217
|
+
stripWrappedParentheses(value) {
|
|
2218
|
+
let nextValue = value.trim();
|
|
2219
|
+
while (this.hasWrappedParentheses(nextValue)) nextValue = nextValue.slice(1, -1).trim();
|
|
2220
|
+
return nextValue;
|
|
2221
|
+
}
|
|
2222
|
+
parseDeclarationType(value) {
|
|
2223
|
+
const trimmed = this.stripWrappedParentheses(value.trim());
|
|
2224
|
+
if (!trimmed) return null;
|
|
2225
|
+
const unionParts = this.splitTopLevel(trimmed, "|");
|
|
2226
|
+
if (unionParts.length > 1) {
|
|
2227
|
+
const types = unionParts.map((part) => this.parseDeclarationType(part)).filter((part) => part !== null);
|
|
2228
|
+
if (types.length !== unionParts.length) return null;
|
|
2229
|
+
return {
|
|
2230
|
+
kind: "union",
|
|
2231
|
+
types: types.flatMap((type) => type.kind === "union" ? type.types : [type])
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
if (trimmed.endsWith("[]")) {
|
|
2235
|
+
const element = this.parseDeclarationType(trimmed.slice(0, -2));
|
|
2236
|
+
if (!element) return null;
|
|
2237
|
+
return {
|
|
2238
|
+
kind: "array",
|
|
2239
|
+
element
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
const arrayMatch = trimmed.match(/^(Array|ReadonlyArray)<([\s\S]+)>$/);
|
|
2243
|
+
if (arrayMatch) {
|
|
2244
|
+
const element = this.parseDeclarationType(arrayMatch[2]);
|
|
2245
|
+
if (!element) return null;
|
|
2246
|
+
return {
|
|
2247
|
+
kind: "array",
|
|
2248
|
+
element
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
if (trimmed === "null") return { kind: "null" };
|
|
2252
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith("\"") && trimmed.endsWith("\"")) return {
|
|
2253
|
+
kind: "string-literal",
|
|
2254
|
+
value: trimmed.slice(1, -1)
|
|
2255
|
+
};
|
|
2256
|
+
if (trimmed === "Record<string, unknown>") return {
|
|
2257
|
+
kind: "named",
|
|
2258
|
+
name: trimmed
|
|
2259
|
+
};
|
|
2260
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(trimmed)) return {
|
|
2261
|
+
kind: "named",
|
|
2262
|
+
name: trimmed
|
|
2263
|
+
};
|
|
2264
|
+
return null;
|
|
2265
|
+
}
|
|
2266
|
+
expandUnion(node) {
|
|
2267
|
+
return node.kind === "union" ? node.types : [node];
|
|
2268
|
+
}
|
|
2269
|
+
isEnumTypeName(value, enums) {
|
|
2270
|
+
return enums.has(value);
|
|
2271
|
+
}
|
|
2272
|
+
isDeclarationNodeAssignable(actual, expected, enums) {
|
|
2273
|
+
if (expected.kind === "named") {
|
|
2274
|
+
if (expected.name === "unknown") return true;
|
|
2275
|
+
if (actual.kind === "named") {
|
|
2276
|
+
if (actual.name === expected.name) return true;
|
|
2277
|
+
if (expected.name === "string" && this.isEnumTypeName(actual.name, enums)) return true;
|
|
2278
|
+
}
|
|
2279
|
+
if (actual.kind === "string-literal") {
|
|
2280
|
+
if (expected.name === "string") return true;
|
|
2281
|
+
if (enums.get(expected.name)?.includes(actual.value)) return true;
|
|
2282
|
+
}
|
|
2283
|
+
return false;
|
|
2284
|
+
}
|
|
2285
|
+
if (expected.kind === "array") return actual.kind === "array" && this.isDeclarationAssignable(actual.element, expected.element, enums);
|
|
2286
|
+
if (expected.kind === "null") return actual.kind === "null";
|
|
2287
|
+
if (expected.kind === "string-literal") return actual.kind === "string-literal" && actual.value === expected.value;
|
|
2288
|
+
return false;
|
|
2289
|
+
}
|
|
2290
|
+
isDeclarationAssignable(actual, expected, enums) {
|
|
2291
|
+
const actualTypes = this.expandUnion(actual);
|
|
2292
|
+
const expectedTypes = this.expandUnion(expected);
|
|
2293
|
+
return actualTypes.every((actualType) => {
|
|
2294
|
+
return expectedTypes.some((expectedType) => {
|
|
2295
|
+
return this.isDeclarationNodeAssignable(actualType, expectedType, enums);
|
|
2296
|
+
});
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2299
|
+
isCompatibleDeclarationType(actualType, expectedType, enums) {
|
|
2300
|
+
const actual = this.parseDeclarationType(actualType);
|
|
2301
|
+
const expected = this.parseDeclarationType(expectedType);
|
|
2302
|
+
if (!actual || !expected) return actualType.replace(/\s+/g, " ").trim() === expectedType.replace(/\s+/g, " ").trim();
|
|
2303
|
+
return this.isDeclarationAssignable(actual, expected, enums);
|
|
2304
|
+
}
|
|
2305
|
+
collectEnumReferencesFromNode(node, enums, collected) {
|
|
2306
|
+
if (node.kind === "named" && enums.has(node.name)) {
|
|
2307
|
+
collected.add(node.name);
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2310
|
+
if (node.kind === "array") {
|
|
2311
|
+
this.collectEnumReferencesFromNode(node.element, enums, collected);
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
if (node.kind === "union") node.types.forEach((type) => this.collectEnumReferencesFromNode(type, enums, collected));
|
|
2315
|
+
}
|
|
2316
|
+
collectEnumReferences(type, enums) {
|
|
2317
|
+
const parsed = this.parseDeclarationType(type);
|
|
2318
|
+
if (!parsed) return [];
|
|
2319
|
+
const collected = /* @__PURE__ */ new Set();
|
|
2320
|
+
this.collectEnumReferencesFromNode(parsed, enums, collected);
|
|
2321
|
+
return [...collected].sort((left, right) => left.localeCompare(right));
|
|
2322
|
+
}
|
|
2323
|
+
syncPrismaEnumImports(modelSource, enumTypes) {
|
|
2324
|
+
if (enumTypes.length === 0) return modelSource;
|
|
2325
|
+
const importRegex = /^import\s+type\s+\{([^}]+)\}\s+from\s+['"]@prisma\/client['"]\s*;?$/m;
|
|
2326
|
+
const existingImport = modelSource.match(importRegex);
|
|
2327
|
+
if (existingImport) {
|
|
2328
|
+
const existingTypes = existingImport[1].split(",").map((value) => value.trim()).filter(Boolean);
|
|
2329
|
+
const mergedTypes = [...new Set([...existingTypes, ...enumTypes])].sort((left, right) => left.localeCompare(right));
|
|
2330
|
+
return modelSource.replace(importRegex, `import type { ${mergedTypes.join(", ")} } from '@prisma/client'`);
|
|
2331
|
+
}
|
|
2332
|
+
const lines = modelSource.split("\n");
|
|
2333
|
+
let insertionIndex = 0;
|
|
2334
|
+
while (insertionIndex < lines.length && lines[insertionIndex].trim().startsWith("import ")) insertionIndex += 1;
|
|
2335
|
+
lines.splice(insertionIndex, 0, `import type { ${enumTypes.join(", ")} } from '@prisma/client'`);
|
|
2336
|
+
return lines.join("\n");
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Parse Prisma enum definitions from a schema and return their member names.
|
|
2340
|
+
*
|
|
2341
|
+
* @param schema The Prisma schema source.
|
|
2342
|
+
* @returns A map of enum names to their declared member names.
|
|
2343
|
+
*/
|
|
2344
|
+
parsePrismaEnums(schema) {
|
|
2345
|
+
const enums = /* @__PURE__ */ new Map();
|
|
2346
|
+
for (const match of schema.matchAll(PRISMA_ENUM_REGEX)) {
|
|
2347
|
+
const enumName = match[1];
|
|
2348
|
+
const values = match[0].split("\n").slice(1, -1).map((line) => line.trim()).filter((line) => Boolean(line) && !line.startsWith("//")).map((line) => {
|
|
2349
|
+
return line.match(/^([A-Za-z][A-Za-z0-9_]*)\b/)?.[1];
|
|
2350
|
+
}).filter((value) => Boolean(value));
|
|
2351
|
+
enums.set(enumName, values);
|
|
2352
|
+
}
|
|
2353
|
+
return enums;
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Resolve the generated TypeScript declaration type for a Prisma field.
|
|
2357
|
+
*
|
|
2358
|
+
* @param fieldType The Prisma field type token.
|
|
2359
|
+
* @param isList Whether the field is declared as a Prisma list.
|
|
2360
|
+
* @param enums Known Prisma enum definitions.
|
|
2361
|
+
* @returns The declaration type to emit, or null when unsupported.
|
|
2362
|
+
*/
|
|
2363
|
+
prismaFieldTypeToTs(fieldType, isList, enums) {
|
|
2364
|
+
const baseType = enums.has(fieldType) ? fieldType : this.prismaTypeToTs(fieldType);
|
|
2365
|
+
if (baseType === "unknown" && !enums.has(fieldType)) return null;
|
|
2366
|
+
return isList ? `Array<${baseType}>` : baseType;
|
|
2367
|
+
}
|
|
2153
2368
|
/**
|
|
2154
2369
|
* Parse the Prisma schema to extract model definitions and their fields, focusing
|
|
2155
2370
|
* on scalar types.
|
|
@@ -2159,6 +2374,7 @@ var CliApp = class {
|
|
|
2159
2374
|
*/
|
|
2160
2375
|
parsePrismaModels(schema) {
|
|
2161
2376
|
const models = [];
|
|
2377
|
+
const enumDefinitions = this.parsePrismaEnums(schema);
|
|
2162
2378
|
const modelRegex = /model\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
|
|
2163
2379
|
const scalarTypes = new Set([
|
|
2164
2380
|
"Int",
|
|
@@ -2179,14 +2395,16 @@ var CliApp = class {
|
|
|
2179
2395
|
body.split("\n").forEach((rawLine) => {
|
|
2180
2396
|
const line = rawLine.trim();
|
|
2181
2397
|
if (!line || line.startsWith("@@") || line.startsWith("//")) return;
|
|
2182
|
-
const fieldMatch = line.match(/^(\w+)\s+([A-Za-z]
|
|
2398
|
+
const fieldMatch = line.match(/^(\w+)\s+([A-Za-z][A-Za-z0-9_]*)(\[\])?(\?)?(?:\s|$)/);
|
|
2183
2399
|
if (!fieldMatch) return;
|
|
2184
2400
|
const fieldType = fieldMatch[2];
|
|
2185
|
-
if (!scalarTypes.has(fieldType)) return;
|
|
2401
|
+
if (!scalarTypes.has(fieldType) && !enumDefinitions.has(fieldType)) return;
|
|
2402
|
+
const declarationType = this.prismaFieldTypeToTs(fieldType, Boolean(fieldMatch[3]), enumDefinitions);
|
|
2403
|
+
if (!declarationType) return;
|
|
2186
2404
|
fields.push({
|
|
2187
2405
|
name: fieldMatch[1],
|
|
2188
|
-
type:
|
|
2189
|
-
nullable: Boolean(fieldMatch[
|
|
2406
|
+
type: declarationType,
|
|
2407
|
+
nullable: Boolean(fieldMatch[4])
|
|
2190
2408
|
});
|
|
2191
2409
|
});
|
|
2192
2410
|
models.push({
|
|
@@ -2207,7 +2425,7 @@ var CliApp = class {
|
|
|
2207
2425
|
* @param declarations A list of attribute declarations to sync.
|
|
2208
2426
|
* @returns An object containing the updated content and a flag indicating if it was updated.
|
|
2209
2427
|
*/
|
|
2210
|
-
syncModelDeclarations(modelSource, declarations) {
|
|
2428
|
+
syncModelDeclarations(modelSource, declarations, enums) {
|
|
2211
2429
|
const lines = modelSource.split("\n");
|
|
2212
2430
|
const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model<.+>\s*\{/.test(line));
|
|
2213
2431
|
if (classIndex < 0) return {
|
|
@@ -2229,16 +2447,38 @@ var CliApp = class {
|
|
|
2229
2447
|
content: modelSource,
|
|
2230
2448
|
updated: false
|
|
2231
2449
|
};
|
|
2232
|
-
const
|
|
2233
|
-
const
|
|
2450
|
+
const withinClass = lines.slice(classIndex + 1, classEndIndex);
|
|
2451
|
+
const existingDeclarations = /* @__PURE__ */ new Map();
|
|
2452
|
+
withinClass.forEach((line) => {
|
|
2453
|
+
const declarationMatch = line.match(/^\s*declare\s+(\w+)\??:\s*([^;\n]+);?\s*$/);
|
|
2454
|
+
if (!declarationMatch) return;
|
|
2455
|
+
existingDeclarations.set(declarationMatch[1], {
|
|
2456
|
+
name: declarationMatch[1],
|
|
2457
|
+
raw: line.trim(),
|
|
2458
|
+
type: declarationMatch[2].trim()
|
|
2459
|
+
});
|
|
2460
|
+
});
|
|
2461
|
+
const withoutDeclares = withinClass.filter((line) => !/^\s*declare\s+\w+\??:\s*[^\n]+$/.test(line));
|
|
2462
|
+
const chosenDeclarations = declarations.map((declaration) => {
|
|
2463
|
+
const expectedType = `${declaration.type}${declaration.nullable ? " | null" : ""}`;
|
|
2464
|
+
const existingDeclaration = existingDeclarations.get(declaration.name);
|
|
2465
|
+
if (existingDeclaration && this.isCompatibleDeclarationType(existingDeclaration.type, expectedType, enums)) return existingDeclaration.raw;
|
|
2466
|
+
return `declare ${declaration.name}: ${expectedType}`;
|
|
2467
|
+
});
|
|
2468
|
+
const rebuiltClass = [...chosenDeclarations.map((declaration) => ` ${declaration}`), ...withoutDeclares];
|
|
2234
2469
|
const content = [
|
|
2235
2470
|
...lines.slice(0, classIndex + 1),
|
|
2236
2471
|
...rebuiltClass,
|
|
2237
2472
|
...lines.slice(classEndIndex)
|
|
2238
2473
|
].join("\n");
|
|
2474
|
+
const enumImports = [...new Set(chosenDeclarations.flatMap((declaration) => {
|
|
2475
|
+
const type = declaration.replace(/^declare\s+\w+\??:\s*/, "").replace(/;$/, "").trim();
|
|
2476
|
+
return this.collectEnumReferences(type, enums);
|
|
2477
|
+
}))].sort((left, right) => left.localeCompare(right));
|
|
2478
|
+
const contentWithImports = this.syncPrismaEnumImports(content, enumImports);
|
|
2239
2479
|
return {
|
|
2240
|
-
content,
|
|
2241
|
-
updated:
|
|
2480
|
+
content: contentWithImports,
|
|
2481
|
+
updated: contentWithImports !== modelSource
|
|
2242
2482
|
};
|
|
2243
2483
|
}
|
|
2244
2484
|
/**
|
|
@@ -2258,6 +2498,7 @@ var CliApp = class {
|
|
|
2258
2498
|
if (!(0, fs.existsSync)(schemaPath)) throw new Error(`Prisma schema file not found: ${schemaPath}`);
|
|
2259
2499
|
if (!(0, fs.existsSync)(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
|
|
2260
2500
|
const schema = (0, fs.readFileSync)(schemaPath, "utf-8");
|
|
2501
|
+
const prismaEnums = this.parsePrismaEnums(schema);
|
|
2261
2502
|
const prismaModels = this.parsePrismaModels(schema);
|
|
2262
2503
|
const modelFiles = (0, fs.readdirSync)(modelsDir).filter((file) => file.endsWith(".ts"));
|
|
2263
2504
|
const updated = [];
|
|
@@ -2277,8 +2518,7 @@ var CliApp = class {
|
|
|
2277
2518
|
skipped.push(filePath);
|
|
2278
2519
|
return;
|
|
2279
2520
|
}
|
|
2280
|
-
const
|
|
2281
|
-
const synced = this.syncModelDeclarations(source, declarations);
|
|
2521
|
+
const synced = this.syncModelDeclarations(source, prismaModel.fields, prismaEnums);
|
|
2282
2522
|
if (!synced.updated) {
|
|
2283
2523
|
skipped.push(filePath);
|
|
2284
2524
|
return;
|
package/dist/index.d.cts
CHANGED
|
@@ -2470,6 +2470,34 @@ declare class CliApp {
|
|
|
2470
2470
|
* @returns The corresponding TypeScript type.
|
|
2471
2471
|
*/
|
|
2472
2472
|
private prismaTypeToTs;
|
|
2473
|
+
private splitTopLevel;
|
|
2474
|
+
private hasWrappedParentheses;
|
|
2475
|
+
private stripWrappedParentheses;
|
|
2476
|
+
private parseDeclarationType;
|
|
2477
|
+
private expandUnion;
|
|
2478
|
+
private isEnumTypeName;
|
|
2479
|
+
private isDeclarationNodeAssignable;
|
|
2480
|
+
private isDeclarationAssignable;
|
|
2481
|
+
private isCompatibleDeclarationType;
|
|
2482
|
+
private collectEnumReferencesFromNode;
|
|
2483
|
+
private collectEnumReferences;
|
|
2484
|
+
private syncPrismaEnumImports;
|
|
2485
|
+
/**
|
|
2486
|
+
* Parse Prisma enum definitions from a schema and return their member names.
|
|
2487
|
+
*
|
|
2488
|
+
* @param schema The Prisma schema source.
|
|
2489
|
+
* @returns A map of enum names to their declared member names.
|
|
2490
|
+
*/
|
|
2491
|
+
private parsePrismaEnums;
|
|
2492
|
+
/**
|
|
2493
|
+
* Resolve the generated TypeScript declaration type for a Prisma field.
|
|
2494
|
+
*
|
|
2495
|
+
* @param fieldType The Prisma field type token.
|
|
2496
|
+
* @param isList Whether the field is declared as a Prisma list.
|
|
2497
|
+
* @param enums Known Prisma enum definitions.
|
|
2498
|
+
* @returns The declaration type to emit, or null when unsupported.
|
|
2499
|
+
*/
|
|
2500
|
+
private prismaFieldTypeToTs;
|
|
2473
2501
|
/**
|
|
2474
2502
|
* Parse the Prisma schema to extract model definitions and their fields, focusing
|
|
2475
2503
|
* on scalar types.
|
package/dist/index.d.mts
CHANGED
|
@@ -2470,6 +2470,34 @@ declare class CliApp {
|
|
|
2470
2470
|
* @returns The corresponding TypeScript type.
|
|
2471
2471
|
*/
|
|
2472
2472
|
private prismaTypeToTs;
|
|
2473
|
+
private splitTopLevel;
|
|
2474
|
+
private hasWrappedParentheses;
|
|
2475
|
+
private stripWrappedParentheses;
|
|
2476
|
+
private parseDeclarationType;
|
|
2477
|
+
private expandUnion;
|
|
2478
|
+
private isEnumTypeName;
|
|
2479
|
+
private isDeclarationNodeAssignable;
|
|
2480
|
+
private isDeclarationAssignable;
|
|
2481
|
+
private isCompatibleDeclarationType;
|
|
2482
|
+
private collectEnumReferencesFromNode;
|
|
2483
|
+
private collectEnumReferences;
|
|
2484
|
+
private syncPrismaEnumImports;
|
|
2485
|
+
/**
|
|
2486
|
+
* Parse Prisma enum definitions from a schema and return their member names.
|
|
2487
|
+
*
|
|
2488
|
+
* @param schema The Prisma schema source.
|
|
2489
|
+
* @returns A map of enum names to their declared member names.
|
|
2490
|
+
*/
|
|
2491
|
+
private parsePrismaEnums;
|
|
2492
|
+
/**
|
|
2493
|
+
* Resolve the generated TypeScript declaration type for a Prisma field.
|
|
2494
|
+
*
|
|
2495
|
+
* @param fieldType The Prisma field type token.
|
|
2496
|
+
* @param isList Whether the field is declared as a Prisma list.
|
|
2497
|
+
* @param enums Known Prisma enum definitions.
|
|
2498
|
+
* @returns The declaration type to emit, or null when unsupported.
|
|
2499
|
+
*/
|
|
2500
|
+
private prismaFieldTypeToTs;
|
|
2473
2501
|
/**
|
|
2474
2502
|
* Parse the Prisma schema to extract model definitions and their fields, focusing
|
|
2475
2503
|
* on scalar types.
|
package/dist/index.mjs
CHANGED
|
@@ -2117,10 +2117,225 @@ var CliApp = class {
|
|
|
2117
2117
|
if (value === "String") return "string";
|
|
2118
2118
|
if (value === "Boolean") return "boolean";
|
|
2119
2119
|
if (value === "DateTime") return "Date";
|
|
2120
|
-
if (value === "Json") return "Record<string, unknown>";
|
|
2120
|
+
if (value === "Json") return "Record<string, unknown> | unknown[]";
|
|
2121
2121
|
if (value === "Bytes") return "Buffer";
|
|
2122
2122
|
return "unknown";
|
|
2123
2123
|
}
|
|
2124
|
+
splitTopLevel(value, delimiter) {
|
|
2125
|
+
const parts = [];
|
|
2126
|
+
let start = 0;
|
|
2127
|
+
let angleDepth = 0;
|
|
2128
|
+
let parenthesisDepth = 0;
|
|
2129
|
+
let quote = null;
|
|
2130
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2131
|
+
const character = value[index];
|
|
2132
|
+
const previous = index > 0 ? value[index - 1] : "";
|
|
2133
|
+
if (quote) {
|
|
2134
|
+
if (character === quote && previous !== "\\") quote = null;
|
|
2135
|
+
continue;
|
|
2136
|
+
}
|
|
2137
|
+
if (character === "'" || character === "\"") {
|
|
2138
|
+
quote = character;
|
|
2139
|
+
continue;
|
|
2140
|
+
}
|
|
2141
|
+
if (character === "<") {
|
|
2142
|
+
angleDepth += 1;
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
if (character === ">") {
|
|
2146
|
+
angleDepth = Math.max(0, angleDepth - 1);
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
if (character === "(") {
|
|
2150
|
+
parenthesisDepth += 1;
|
|
2151
|
+
continue;
|
|
2152
|
+
}
|
|
2153
|
+
if (character === ")") {
|
|
2154
|
+
parenthesisDepth = Math.max(0, parenthesisDepth - 1);
|
|
2155
|
+
continue;
|
|
2156
|
+
}
|
|
2157
|
+
if (character === delimiter && angleDepth === 0 && parenthesisDepth === 0) {
|
|
2158
|
+
parts.push(value.slice(start, index).trim());
|
|
2159
|
+
start = index + 1;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
parts.push(value.slice(start).trim());
|
|
2163
|
+
return parts.filter(Boolean);
|
|
2164
|
+
}
|
|
2165
|
+
hasWrappedParentheses(value) {
|
|
2166
|
+
if (!value.startsWith("(") || !value.endsWith(")")) return false;
|
|
2167
|
+
let depth = 0;
|
|
2168
|
+
let quote = null;
|
|
2169
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2170
|
+
const character = value[index];
|
|
2171
|
+
const previous = index > 0 ? value[index - 1] : "";
|
|
2172
|
+
if (quote) {
|
|
2173
|
+
if (character === quote && previous !== "\\") quote = null;
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
if (character === "'" || character === "\"") {
|
|
2177
|
+
quote = character;
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
if (character === "(") depth += 1;
|
|
2181
|
+
if (character === ")") {
|
|
2182
|
+
depth -= 1;
|
|
2183
|
+
if (depth === 0 && index < value.length - 1) return false;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
return depth === 0;
|
|
2187
|
+
}
|
|
2188
|
+
stripWrappedParentheses(value) {
|
|
2189
|
+
let nextValue = value.trim();
|
|
2190
|
+
while (this.hasWrappedParentheses(nextValue)) nextValue = nextValue.slice(1, -1).trim();
|
|
2191
|
+
return nextValue;
|
|
2192
|
+
}
|
|
2193
|
+
parseDeclarationType(value) {
|
|
2194
|
+
const trimmed = this.stripWrappedParentheses(value.trim());
|
|
2195
|
+
if (!trimmed) return null;
|
|
2196
|
+
const unionParts = this.splitTopLevel(trimmed, "|");
|
|
2197
|
+
if (unionParts.length > 1) {
|
|
2198
|
+
const types = unionParts.map((part) => this.parseDeclarationType(part)).filter((part) => part !== null);
|
|
2199
|
+
if (types.length !== unionParts.length) return null;
|
|
2200
|
+
return {
|
|
2201
|
+
kind: "union",
|
|
2202
|
+
types: types.flatMap((type) => type.kind === "union" ? type.types : [type])
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
if (trimmed.endsWith("[]")) {
|
|
2206
|
+
const element = this.parseDeclarationType(trimmed.slice(0, -2));
|
|
2207
|
+
if (!element) return null;
|
|
2208
|
+
return {
|
|
2209
|
+
kind: "array",
|
|
2210
|
+
element
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
const arrayMatch = trimmed.match(/^(Array|ReadonlyArray)<([\s\S]+)>$/);
|
|
2214
|
+
if (arrayMatch) {
|
|
2215
|
+
const element = this.parseDeclarationType(arrayMatch[2]);
|
|
2216
|
+
if (!element) return null;
|
|
2217
|
+
return {
|
|
2218
|
+
kind: "array",
|
|
2219
|
+
element
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
if (trimmed === "null") return { kind: "null" };
|
|
2223
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith("\"") && trimmed.endsWith("\"")) return {
|
|
2224
|
+
kind: "string-literal",
|
|
2225
|
+
value: trimmed.slice(1, -1)
|
|
2226
|
+
};
|
|
2227
|
+
if (trimmed === "Record<string, unknown>") return {
|
|
2228
|
+
kind: "named",
|
|
2229
|
+
name: trimmed
|
|
2230
|
+
};
|
|
2231
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(trimmed)) return {
|
|
2232
|
+
kind: "named",
|
|
2233
|
+
name: trimmed
|
|
2234
|
+
};
|
|
2235
|
+
return null;
|
|
2236
|
+
}
|
|
2237
|
+
expandUnion(node) {
|
|
2238
|
+
return node.kind === "union" ? node.types : [node];
|
|
2239
|
+
}
|
|
2240
|
+
isEnumTypeName(value, enums) {
|
|
2241
|
+
return enums.has(value);
|
|
2242
|
+
}
|
|
2243
|
+
isDeclarationNodeAssignable(actual, expected, enums) {
|
|
2244
|
+
if (expected.kind === "named") {
|
|
2245
|
+
if (expected.name === "unknown") return true;
|
|
2246
|
+
if (actual.kind === "named") {
|
|
2247
|
+
if (actual.name === expected.name) return true;
|
|
2248
|
+
if (expected.name === "string" && this.isEnumTypeName(actual.name, enums)) return true;
|
|
2249
|
+
}
|
|
2250
|
+
if (actual.kind === "string-literal") {
|
|
2251
|
+
if (expected.name === "string") return true;
|
|
2252
|
+
if (enums.get(expected.name)?.includes(actual.value)) return true;
|
|
2253
|
+
}
|
|
2254
|
+
return false;
|
|
2255
|
+
}
|
|
2256
|
+
if (expected.kind === "array") return actual.kind === "array" && this.isDeclarationAssignable(actual.element, expected.element, enums);
|
|
2257
|
+
if (expected.kind === "null") return actual.kind === "null";
|
|
2258
|
+
if (expected.kind === "string-literal") return actual.kind === "string-literal" && actual.value === expected.value;
|
|
2259
|
+
return false;
|
|
2260
|
+
}
|
|
2261
|
+
isDeclarationAssignable(actual, expected, enums) {
|
|
2262
|
+
const actualTypes = this.expandUnion(actual);
|
|
2263
|
+
const expectedTypes = this.expandUnion(expected);
|
|
2264
|
+
return actualTypes.every((actualType) => {
|
|
2265
|
+
return expectedTypes.some((expectedType) => {
|
|
2266
|
+
return this.isDeclarationNodeAssignable(actualType, expectedType, enums);
|
|
2267
|
+
});
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
isCompatibleDeclarationType(actualType, expectedType, enums) {
|
|
2271
|
+
const actual = this.parseDeclarationType(actualType);
|
|
2272
|
+
const expected = this.parseDeclarationType(expectedType);
|
|
2273
|
+
if (!actual || !expected) return actualType.replace(/\s+/g, " ").trim() === expectedType.replace(/\s+/g, " ").trim();
|
|
2274
|
+
return this.isDeclarationAssignable(actual, expected, enums);
|
|
2275
|
+
}
|
|
2276
|
+
collectEnumReferencesFromNode(node, enums, collected) {
|
|
2277
|
+
if (node.kind === "named" && enums.has(node.name)) {
|
|
2278
|
+
collected.add(node.name);
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
if (node.kind === "array") {
|
|
2282
|
+
this.collectEnumReferencesFromNode(node.element, enums, collected);
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
if (node.kind === "union") node.types.forEach((type) => this.collectEnumReferencesFromNode(type, enums, collected));
|
|
2286
|
+
}
|
|
2287
|
+
collectEnumReferences(type, enums) {
|
|
2288
|
+
const parsed = this.parseDeclarationType(type);
|
|
2289
|
+
if (!parsed) return [];
|
|
2290
|
+
const collected = /* @__PURE__ */ new Set();
|
|
2291
|
+
this.collectEnumReferencesFromNode(parsed, enums, collected);
|
|
2292
|
+
return [...collected].sort((left, right) => left.localeCompare(right));
|
|
2293
|
+
}
|
|
2294
|
+
syncPrismaEnumImports(modelSource, enumTypes) {
|
|
2295
|
+
if (enumTypes.length === 0) return modelSource;
|
|
2296
|
+
const importRegex = /^import\s+type\s+\{([^}]+)\}\s+from\s+['"]@prisma\/client['"]\s*;?$/m;
|
|
2297
|
+
const existingImport = modelSource.match(importRegex);
|
|
2298
|
+
if (existingImport) {
|
|
2299
|
+
const existingTypes = existingImport[1].split(",").map((value) => value.trim()).filter(Boolean);
|
|
2300
|
+
const mergedTypes = [...new Set([...existingTypes, ...enumTypes])].sort((left, right) => left.localeCompare(right));
|
|
2301
|
+
return modelSource.replace(importRegex, `import type { ${mergedTypes.join(", ")} } from '@prisma/client'`);
|
|
2302
|
+
}
|
|
2303
|
+
const lines = modelSource.split("\n");
|
|
2304
|
+
let insertionIndex = 0;
|
|
2305
|
+
while (insertionIndex < lines.length && lines[insertionIndex].trim().startsWith("import ")) insertionIndex += 1;
|
|
2306
|
+
lines.splice(insertionIndex, 0, `import type { ${enumTypes.join(", ")} } from '@prisma/client'`);
|
|
2307
|
+
return lines.join("\n");
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* Parse Prisma enum definitions from a schema and return their member names.
|
|
2311
|
+
*
|
|
2312
|
+
* @param schema The Prisma schema source.
|
|
2313
|
+
* @returns A map of enum names to their declared member names.
|
|
2314
|
+
*/
|
|
2315
|
+
parsePrismaEnums(schema) {
|
|
2316
|
+
const enums = /* @__PURE__ */ new Map();
|
|
2317
|
+
for (const match of schema.matchAll(PRISMA_ENUM_REGEX)) {
|
|
2318
|
+
const enumName = match[1];
|
|
2319
|
+
const values = match[0].split("\n").slice(1, -1).map((line) => line.trim()).filter((line) => Boolean(line) && !line.startsWith("//")).map((line) => {
|
|
2320
|
+
return line.match(/^([A-Za-z][A-Za-z0-9_]*)\b/)?.[1];
|
|
2321
|
+
}).filter((value) => Boolean(value));
|
|
2322
|
+
enums.set(enumName, values);
|
|
2323
|
+
}
|
|
2324
|
+
return enums;
|
|
2325
|
+
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Resolve the generated TypeScript declaration type for a Prisma field.
|
|
2328
|
+
*
|
|
2329
|
+
* @param fieldType The Prisma field type token.
|
|
2330
|
+
* @param isList Whether the field is declared as a Prisma list.
|
|
2331
|
+
* @param enums Known Prisma enum definitions.
|
|
2332
|
+
* @returns The declaration type to emit, or null when unsupported.
|
|
2333
|
+
*/
|
|
2334
|
+
prismaFieldTypeToTs(fieldType, isList, enums) {
|
|
2335
|
+
const baseType = enums.has(fieldType) ? fieldType : this.prismaTypeToTs(fieldType);
|
|
2336
|
+
if (baseType === "unknown" && !enums.has(fieldType)) return null;
|
|
2337
|
+
return isList ? `Array<${baseType}>` : baseType;
|
|
2338
|
+
}
|
|
2124
2339
|
/**
|
|
2125
2340
|
* Parse the Prisma schema to extract model definitions and their fields, focusing
|
|
2126
2341
|
* on scalar types.
|
|
@@ -2130,6 +2345,7 @@ var CliApp = class {
|
|
|
2130
2345
|
*/
|
|
2131
2346
|
parsePrismaModels(schema) {
|
|
2132
2347
|
const models = [];
|
|
2348
|
+
const enumDefinitions = this.parsePrismaEnums(schema);
|
|
2133
2349
|
const modelRegex = /model\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
|
|
2134
2350
|
const scalarTypes = new Set([
|
|
2135
2351
|
"Int",
|
|
@@ -2150,14 +2366,16 @@ var CliApp = class {
|
|
|
2150
2366
|
body.split("\n").forEach((rawLine) => {
|
|
2151
2367
|
const line = rawLine.trim();
|
|
2152
2368
|
if (!line || line.startsWith("@@") || line.startsWith("//")) return;
|
|
2153
|
-
const fieldMatch = line.match(/^(\w+)\s+([A-Za-z]
|
|
2369
|
+
const fieldMatch = line.match(/^(\w+)\s+([A-Za-z][A-Za-z0-9_]*)(\[\])?(\?)?(?:\s|$)/);
|
|
2154
2370
|
if (!fieldMatch) return;
|
|
2155
2371
|
const fieldType = fieldMatch[2];
|
|
2156
|
-
if (!scalarTypes.has(fieldType)) return;
|
|
2372
|
+
if (!scalarTypes.has(fieldType) && !enumDefinitions.has(fieldType)) return;
|
|
2373
|
+
const declarationType = this.prismaFieldTypeToTs(fieldType, Boolean(fieldMatch[3]), enumDefinitions);
|
|
2374
|
+
if (!declarationType) return;
|
|
2157
2375
|
fields.push({
|
|
2158
2376
|
name: fieldMatch[1],
|
|
2159
|
-
type:
|
|
2160
|
-
nullable: Boolean(fieldMatch[
|
|
2377
|
+
type: declarationType,
|
|
2378
|
+
nullable: Boolean(fieldMatch[4])
|
|
2161
2379
|
});
|
|
2162
2380
|
});
|
|
2163
2381
|
models.push({
|
|
@@ -2178,7 +2396,7 @@ var CliApp = class {
|
|
|
2178
2396
|
* @param declarations A list of attribute declarations to sync.
|
|
2179
2397
|
* @returns An object containing the updated content and a flag indicating if it was updated.
|
|
2180
2398
|
*/
|
|
2181
|
-
syncModelDeclarations(modelSource, declarations) {
|
|
2399
|
+
syncModelDeclarations(modelSource, declarations, enums) {
|
|
2182
2400
|
const lines = modelSource.split("\n");
|
|
2183
2401
|
const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model<.+>\s*\{/.test(line));
|
|
2184
2402
|
if (classIndex < 0) return {
|
|
@@ -2200,16 +2418,38 @@ var CliApp = class {
|
|
|
2200
2418
|
content: modelSource,
|
|
2201
2419
|
updated: false
|
|
2202
2420
|
};
|
|
2203
|
-
const
|
|
2204
|
-
const
|
|
2421
|
+
const withinClass = lines.slice(classIndex + 1, classEndIndex);
|
|
2422
|
+
const existingDeclarations = /* @__PURE__ */ new Map();
|
|
2423
|
+
withinClass.forEach((line) => {
|
|
2424
|
+
const declarationMatch = line.match(/^\s*declare\s+(\w+)\??:\s*([^;\n]+);?\s*$/);
|
|
2425
|
+
if (!declarationMatch) return;
|
|
2426
|
+
existingDeclarations.set(declarationMatch[1], {
|
|
2427
|
+
name: declarationMatch[1],
|
|
2428
|
+
raw: line.trim(),
|
|
2429
|
+
type: declarationMatch[2].trim()
|
|
2430
|
+
});
|
|
2431
|
+
});
|
|
2432
|
+
const withoutDeclares = withinClass.filter((line) => !/^\s*declare\s+\w+\??:\s*[^\n]+$/.test(line));
|
|
2433
|
+
const chosenDeclarations = declarations.map((declaration) => {
|
|
2434
|
+
const expectedType = `${declaration.type}${declaration.nullable ? " | null" : ""}`;
|
|
2435
|
+
const existingDeclaration = existingDeclarations.get(declaration.name);
|
|
2436
|
+
if (existingDeclaration && this.isCompatibleDeclarationType(existingDeclaration.type, expectedType, enums)) return existingDeclaration.raw;
|
|
2437
|
+
return `declare ${declaration.name}: ${expectedType}`;
|
|
2438
|
+
});
|
|
2439
|
+
const rebuiltClass = [...chosenDeclarations.map((declaration) => ` ${declaration}`), ...withoutDeclares];
|
|
2205
2440
|
const content = [
|
|
2206
2441
|
...lines.slice(0, classIndex + 1),
|
|
2207
2442
|
...rebuiltClass,
|
|
2208
2443
|
...lines.slice(classEndIndex)
|
|
2209
2444
|
].join("\n");
|
|
2445
|
+
const enumImports = [...new Set(chosenDeclarations.flatMap((declaration) => {
|
|
2446
|
+
const type = declaration.replace(/^declare\s+\w+\??:\s*/, "").replace(/;$/, "").trim();
|
|
2447
|
+
return this.collectEnumReferences(type, enums);
|
|
2448
|
+
}))].sort((left, right) => left.localeCompare(right));
|
|
2449
|
+
const contentWithImports = this.syncPrismaEnumImports(content, enumImports);
|
|
2210
2450
|
return {
|
|
2211
|
-
content,
|
|
2212
|
-
updated:
|
|
2451
|
+
content: contentWithImports,
|
|
2452
|
+
updated: contentWithImports !== modelSource
|
|
2213
2453
|
};
|
|
2214
2454
|
}
|
|
2215
2455
|
/**
|
|
@@ -2229,6 +2469,7 @@ var CliApp = class {
|
|
|
2229
2469
|
if (!existsSync$1(schemaPath)) throw new Error(`Prisma schema file not found: ${schemaPath}`);
|
|
2230
2470
|
if (!existsSync$1(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
|
|
2231
2471
|
const schema = readFileSync$1(schemaPath, "utf-8");
|
|
2472
|
+
const prismaEnums = this.parsePrismaEnums(schema);
|
|
2232
2473
|
const prismaModels = this.parsePrismaModels(schema);
|
|
2233
2474
|
const modelFiles = readdirSync$1(modelsDir).filter((file) => file.endsWith(".ts"));
|
|
2234
2475
|
const updated = [];
|
|
@@ -2248,8 +2489,7 @@ var CliApp = class {
|
|
|
2248
2489
|
skipped.push(filePath);
|
|
2249
2490
|
return;
|
|
2250
2491
|
}
|
|
2251
|
-
const
|
|
2252
|
-
const synced = this.syncModelDeclarations(source, declarations);
|
|
2492
|
+
const synced = this.syncModelDeclarations(source, prismaModel.fields, prismaEnums);
|
|
2253
2493
|
if (!synced.updated) {
|
|
2254
2494
|
skipped.push(filePath);
|
|
2255
2495
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arkormx",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Modern TypeScript-first ORM for Node.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"orm",
|
|
@@ -48,14 +48,14 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@eslint/js": "^10.0.1",
|
|
50
50
|
"@eslint/markdown": "^7.5.1",
|
|
51
|
-
"@prisma/adapter-pg": "^7.4.2",
|
|
52
51
|
"@types/express": "^4.17.21",
|
|
53
52
|
"@types/node": "^20.10.6",
|
|
53
|
+
"@prisma/adapter-pg": "^7.6.0",
|
|
54
54
|
"@vitest/coverage-v8": "4.0.18",
|
|
55
55
|
"barrelize": "^1.7.3",
|
|
56
56
|
"eslint": "^10.0.0",
|
|
57
57
|
"pg": "^8.19.0",
|
|
58
|
-
"prisma": "^7.
|
|
58
|
+
"prisma": "^7.6.0",
|
|
59
59
|
"tsdown": "^0.20.3",
|
|
60
60
|
"tsx": "^4.21.0",
|
|
61
61
|
"typescript": "^5.3.3",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"dotenv": "^17.3.1"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@prisma/client": "^7.
|
|
78
|
+
"@prisma/client": "^7.6.0"
|
|
79
79
|
},
|
|
80
80
|
"scripts": {
|
|
81
81
|
"cmd": "tsx src/cli/index.ts",
|