postgresdk 0.7.6 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +211 -14
- package/dist/emit-client.d.ts +7 -2
- package/dist/emit-include-methods.d.ts +17 -0
- package/dist/index.js +211 -14
- package/dist/types.d.ts +3 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1914,18 +1914,214 @@ ${hasAuth ? `
|
|
|
1914
1914
|
|
|
1915
1915
|
// src/emit-client.ts
|
|
1916
1916
|
init_utils();
|
|
1917
|
-
|
|
1917
|
+
|
|
1918
|
+
// src/emit-include-methods.ts
|
|
1919
|
+
init_utils();
|
|
1920
|
+
function isJunctionTable(table) {
|
|
1921
|
+
if (!table.name.includes("_"))
|
|
1922
|
+
return false;
|
|
1923
|
+
const fkColumns = new Set(table.fks.flatMap((fk) => fk.from));
|
|
1924
|
+
const nonPkColumns = table.columns.filter((c) => !table.pk.includes(c.name));
|
|
1925
|
+
return nonPkColumns.every((c) => fkColumns.has(c.name));
|
|
1926
|
+
}
|
|
1927
|
+
function pathToMethodSuffix(path) {
|
|
1928
|
+
return "With" + path.map((p) => pascal(p)).join("And");
|
|
1929
|
+
}
|
|
1930
|
+
function buildReturnType(baseTable, path, isMany, targets, graph) {
|
|
1931
|
+
const BaseType = `Select${pascal(baseTable)}`;
|
|
1932
|
+
if (path.length === 0)
|
|
1933
|
+
return BaseType;
|
|
1934
|
+
let type = BaseType;
|
|
1935
|
+
let currentTable = baseTable;
|
|
1936
|
+
const parts = [];
|
|
1937
|
+
for (let i = 0;i < path.length; i++) {
|
|
1938
|
+
const key = path[i];
|
|
1939
|
+
const target = targets[i];
|
|
1940
|
+
if (!key || !target)
|
|
1941
|
+
continue;
|
|
1942
|
+
const targetType = `Select${pascal(target)}`;
|
|
1943
|
+
if (i === 0) {
|
|
1944
|
+
parts.push(`${key}: ${isMany[i] ? `${targetType}[]` : targetType}`);
|
|
1945
|
+
} else {
|
|
1946
|
+
let nestedType = targetType;
|
|
1947
|
+
for (let j = i;j < path.length; j++) {
|
|
1948
|
+
if (j > i) {
|
|
1949
|
+
const nestedKey = path[j];
|
|
1950
|
+
const nestedTarget = targets[j];
|
|
1951
|
+
if (!nestedKey || !nestedTarget)
|
|
1952
|
+
continue;
|
|
1953
|
+
const nestedTargetType = `Select${pascal(nestedTarget)}`;
|
|
1954
|
+
nestedType = `${nestedType} & { ${nestedKey}: ${isMany[j] ? `${nestedTargetType}[]` : nestedTargetType} }`;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
const prevKey = path[i - 1];
|
|
1958
|
+
const prevTarget = targets[i - 1];
|
|
1959
|
+
if (prevKey && prevTarget) {
|
|
1960
|
+
parts[parts.length - 1] = `${prevKey}: ${isMany[i - 1] ? `(Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} })[]` : `Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} }`}`;
|
|
1961
|
+
}
|
|
1962
|
+
break;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return `${type} & { ${parts.join("; ")} }`;
|
|
1966
|
+
}
|
|
1967
|
+
function buildIncludeSpec(path) {
|
|
1968
|
+
if (path.length === 0)
|
|
1969
|
+
return {};
|
|
1970
|
+
if (path.length === 1)
|
|
1971
|
+
return { [path[0]]: true };
|
|
1972
|
+
let spec = true;
|
|
1973
|
+
for (let i = path.length - 1;i > 0; i--) {
|
|
1974
|
+
const key = path[i];
|
|
1975
|
+
if (!key)
|
|
1976
|
+
continue;
|
|
1977
|
+
spec = { [key]: spec };
|
|
1978
|
+
}
|
|
1979
|
+
const rootKey = path[0];
|
|
1980
|
+
return rootKey ? { [rootKey]: spec } : {};
|
|
1981
|
+
}
|
|
1982
|
+
function generateIncludeMethods(table, graph, opts, allTables) {
|
|
1983
|
+
const methods = [];
|
|
1984
|
+
const baseTableName = table.name;
|
|
1985
|
+
if (opts.skipJunctionTables && isJunctionTable(table)) {
|
|
1986
|
+
return methods;
|
|
1987
|
+
}
|
|
1988
|
+
const edges = graph[baseTableName] || {};
|
|
1989
|
+
function explore(currentTable, path, isMany, targets, visited, depth) {
|
|
1990
|
+
if (depth > opts.maxDepth)
|
|
1991
|
+
return;
|
|
1992
|
+
const currentEdges = graph[currentTable] || {};
|
|
1993
|
+
for (const [key, edge] of Object.entries(currentEdges)) {
|
|
1994
|
+
if (visited.has(edge.target))
|
|
1995
|
+
continue;
|
|
1996
|
+
if (opts.skipJunctionTables && allTables) {
|
|
1997
|
+
const targetTable = allTables.find((t) => t.name === edge.target);
|
|
1998
|
+
if (targetTable && isJunctionTable(targetTable)) {
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
const newPath = [...path, key];
|
|
2003
|
+
const newIsMany = [...isMany, edge.kind === "many"];
|
|
2004
|
+
const newTargets = [...targets, edge.target];
|
|
2005
|
+
const methodSuffix = pathToMethodSuffix(newPath);
|
|
2006
|
+
methods.push({
|
|
2007
|
+
name: `list${methodSuffix}`,
|
|
2008
|
+
path: newPath,
|
|
2009
|
+
isMany: newIsMany,
|
|
2010
|
+
targets: newTargets,
|
|
2011
|
+
returnType: `(${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]`,
|
|
2012
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
2013
|
+
});
|
|
2014
|
+
methods.push({
|
|
2015
|
+
name: `getByPk${methodSuffix}`,
|
|
2016
|
+
path: newPath,
|
|
2017
|
+
isMany: newIsMany,
|
|
2018
|
+
targets: newTargets,
|
|
2019
|
+
returnType: `${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)} | null`,
|
|
2020
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
2021
|
+
});
|
|
2022
|
+
explore(edge.target, newPath, newIsMany, newTargets, new Set([...visited, edge.target]), depth + 1);
|
|
2023
|
+
}
|
|
2024
|
+
if (depth === 1 && Object.keys(currentEdges).length > 1 && Object.keys(currentEdges).length <= 3) {
|
|
2025
|
+
const edgeEntries = Object.entries(currentEdges);
|
|
2026
|
+
if (edgeEntries.length >= 2) {
|
|
2027
|
+
for (let i = 0;i < edgeEntries.length - 1; i++) {
|
|
2028
|
+
for (let j = i + 1;j < edgeEntries.length; j++) {
|
|
2029
|
+
const entry1 = edgeEntries[i];
|
|
2030
|
+
const entry2 = edgeEntries[j];
|
|
2031
|
+
if (!entry1 || !entry2)
|
|
2032
|
+
continue;
|
|
2033
|
+
const [key1, edge1] = entry1;
|
|
2034
|
+
const [key2, edge2] = entry2;
|
|
2035
|
+
if (opts.skipJunctionTables && (edge1.target.includes("_") || edge2.target.includes("_"))) {
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
const combinedPath = [key1, key2];
|
|
2039
|
+
const combinedSuffix = `With${pascal(key1)}And${pascal(key2)}`;
|
|
2040
|
+
const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}`}`;
|
|
2041
|
+
const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}`}`;
|
|
2042
|
+
methods.push({
|
|
2043
|
+
name: `list${combinedSuffix}`,
|
|
2044
|
+
path: combinedPath,
|
|
2045
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
2046
|
+
targets: [edge1.target, edge2.target],
|
|
2047
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]`,
|
|
2048
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
2049
|
+
});
|
|
2050
|
+
methods.push({
|
|
2051
|
+
name: `getByPk${combinedSuffix}`,
|
|
2052
|
+
path: combinedPath,
|
|
2053
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
2054
|
+
targets: [edge1.target, edge2.target],
|
|
2055
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} }) | null`,
|
|
2056
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
explore(baseTableName, [], [], [], new Set([baseTableName]), 1);
|
|
2064
|
+
return methods;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// src/emit-client.ts
|
|
2068
|
+
function emitClient(table, graph, opts, model) {
|
|
1918
2069
|
const Type = pascal(table.name);
|
|
1919
|
-
const ext = useJsExtensions ? ".js" : "";
|
|
2070
|
+
const ext = opts.useJsExtensions ? ".js" : "";
|
|
1920
2071
|
const pkCols = Array.isArray(table.pk) ? table.pk : table.pk ? [table.pk] : [];
|
|
1921
2072
|
const safePk = pkCols.length ? pkCols : ["id"];
|
|
1922
2073
|
const hasCompositePk = safePk.length > 1;
|
|
1923
2074
|
const pkType = hasCompositePk ? `{ ${safePk.map((c) => `${c}: string`).join("; ")} }` : `string`;
|
|
1924
2075
|
const pkPathExpr = hasCompositePk ? safePk.map((c) => `pk.${c}`).join(` + "/" + `) : `pk`;
|
|
2076
|
+
const allTables = model ? Object.values(model.tables) : undefined;
|
|
2077
|
+
const includeMethods = generateIncludeMethods(table, graph, {
|
|
2078
|
+
maxDepth: opts.includeMethodsDepth ?? 2,
|
|
2079
|
+
skipJunctionTables: opts.skipJunctionTables ?? true
|
|
2080
|
+
}, allTables);
|
|
2081
|
+
const importedTypes = new Set;
|
|
2082
|
+
importedTypes.add(table.name);
|
|
2083
|
+
for (const method of includeMethods) {
|
|
2084
|
+
for (const target of method.targets) {
|
|
2085
|
+
importedTypes.add(target);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
|
|
2089
|
+
const otherTableImports = [];
|
|
2090
|
+
for (const target of Array.from(importedTypes)) {
|
|
2091
|
+
if (target !== table.name) {
|
|
2092
|
+
otherTableImports.push(`import type { Select${pascal(target)} } from "./types/${target}${ext}";`);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
let includeMethodsCode = "";
|
|
2096
|
+
for (const method of includeMethods) {
|
|
2097
|
+
const isGetByPk = method.name.startsWith("getByPk");
|
|
2098
|
+
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: any; orderBy?: string; order?: "asc" | "desc"; }, "include">`;
|
|
2099
|
+
if (isGetByPk) {
|
|
2100
|
+
const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
|
|
2101
|
+
const baseReturnType = method.returnType.replace(" | null", "");
|
|
2102
|
+
includeMethodsCode += `
|
|
2103
|
+
async ${method.name}(pk: ${pkType}): Promise<${method.returnType}> {
|
|
2104
|
+
const results = await this.post<${baseReturnType}[]>(\`\${this.resource}/list\`, {
|
|
2105
|
+
where: ${pkWhere},
|
|
2106
|
+
include: ${JSON.stringify(method.includeSpec)},
|
|
2107
|
+
limit: 1
|
|
2108
|
+
});
|
|
2109
|
+
return (results[0] as ${baseReturnType}) ?? null;
|
|
2110
|
+
}
|
|
2111
|
+
`;
|
|
2112
|
+
} else {
|
|
2113
|
+
includeMethodsCode += `
|
|
2114
|
+
async ${method.name}(${baseParams}): Promise<${method.returnType}> {
|
|
2115
|
+
return this.post<${method.returnType}>(\`\${this.resource}/list\`, { ...params, include: ${JSON.stringify(method.includeSpec)} });
|
|
2116
|
+
}
|
|
2117
|
+
`;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
1925
2120
|
return `/* Generated. Do not edit. */
|
|
1926
2121
|
import { BaseClient } from "./base-client${ext}";
|
|
1927
|
-
|
|
1928
|
-
|
|
2122
|
+
${typeImports}
|
|
2123
|
+
${otherTableImports.join(`
|
|
2124
|
+
`)}
|
|
1929
2125
|
|
|
1930
2126
|
/**
|
|
1931
2127
|
* Client for ${table.name} table operations
|
|
@@ -1943,7 +2139,6 @@ export class ${Type}Client extends BaseClient {
|
|
|
1943
2139
|
}
|
|
1944
2140
|
|
|
1945
2141
|
async list(params?: {
|
|
1946
|
-
include?: ${Type}IncludeSpec;
|
|
1947
2142
|
limit?: number;
|
|
1948
2143
|
offset?: number;
|
|
1949
2144
|
where?: any;
|
|
@@ -1962,7 +2157,7 @@ export class ${Type}Client extends BaseClient {
|
|
|
1962
2157
|
const path = ${pkPathExpr};
|
|
1963
2158
|
return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
1964
2159
|
}
|
|
1965
|
-
}
|
|
2160
|
+
${includeMethodsCode}}
|
|
1966
2161
|
`;
|
|
1967
2162
|
}
|
|
1968
2163
|
function emitClientIndex(tables, useJsExtensions) {
|
|
@@ -2006,8 +2201,6 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
|
|
|
2006
2201
|
|
|
2007
2202
|
`;
|
|
2008
2203
|
out += `export { BaseClient } from "./base-client${ext}";
|
|
2009
|
-
`;
|
|
2010
|
-
out += `export * from "./include-spec${ext}";
|
|
2011
2204
|
`;
|
|
2012
2205
|
out += `
|
|
2013
2206
|
// Zod schemas for form validation
|
|
@@ -3084,12 +3277,12 @@ function emitTableTest(table, model, clientPath, framework = "vitest") {
|
|
|
3084
3277
|
const Type = pascal(table.name);
|
|
3085
3278
|
const tableName = table.name;
|
|
3086
3279
|
const imports = getFrameworkImports(framework);
|
|
3087
|
-
const
|
|
3280
|
+
const isJunctionTable2 = table.pk.length > 1;
|
|
3088
3281
|
const hasForeignKeys = table.fks.length > 0;
|
|
3089
3282
|
const foreignKeySetup = hasForeignKeys ? generateForeignKeySetup(table, model, clientPath) : null;
|
|
3090
3283
|
const sampleData = generateSampleDataFromSchema(table, hasForeignKeys);
|
|
3091
3284
|
const updateData = generateUpdateDataFromSchema(table);
|
|
3092
|
-
if (
|
|
3285
|
+
if (isJunctionTable2) {
|
|
3093
3286
|
return `${imports}
|
|
3094
3287
|
import { SDK } from '${clientPath}';
|
|
3095
3288
|
import type { Insert${Type}, Update${Type}, Select${Type} } from '${clientPath}/types/${tableName}';
|
|
@@ -3893,11 +4086,11 @@ async function generate(configPath) {
|
|
|
3893
4086
|
files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
|
|
3894
4087
|
files.push({
|
|
3895
4088
|
path: join(serverDir, "include-builder.ts"),
|
|
3896
|
-
content: emitIncludeBuilder(graph, cfg.
|
|
4089
|
+
content: emitIncludeBuilder(graph, cfg.includeMethodsDepth || 2)
|
|
3897
4090
|
});
|
|
3898
4091
|
files.push({
|
|
3899
4092
|
path: join(serverDir, "include-loader.ts"),
|
|
3900
|
-
content: emitIncludeLoader(graph, model, cfg.
|
|
4093
|
+
content: emitIncludeLoader(graph, model, cfg.includeMethodsDepth || 2, cfg.useJsExtensions)
|
|
3901
4094
|
});
|
|
3902
4095
|
files.push({ path: join(serverDir, "logger.ts"), content: emitLogger() });
|
|
3903
4096
|
if (normalizedAuth?.strategy && normalizedAuth.strategy !== "none") {
|
|
@@ -3923,7 +4116,7 @@ async function generate(configPath) {
|
|
|
3923
4116
|
if (serverFramework === "hono") {
|
|
3924
4117
|
routeContent = emitHonoRoutes(table, graph, {
|
|
3925
4118
|
softDeleteColumn: cfg.softDeleteColumn || null,
|
|
3926
|
-
includeDepthLimit: cfg.
|
|
4119
|
+
includeDepthLimit: cfg.includeMethodsDepth || 2,
|
|
3927
4120
|
authStrategy: normalizedAuth?.strategy,
|
|
3928
4121
|
useJsExtensions: cfg.useJsExtensions
|
|
3929
4122
|
});
|
|
@@ -3936,7 +4129,11 @@ async function generate(configPath) {
|
|
|
3936
4129
|
});
|
|
3937
4130
|
files.push({
|
|
3938
4131
|
path: join(clientDir, `${table.name}.ts`),
|
|
3939
|
-
content: emitClient(table,
|
|
4132
|
+
content: emitClient(table, graph, {
|
|
4133
|
+
useJsExtensions: cfg.useJsExtensionsClient,
|
|
4134
|
+
includeMethodsDepth: cfg.includeMethodsDepth ?? 2,
|
|
4135
|
+
skipJunctionTables: cfg.skipJunctionTables ?? true
|
|
4136
|
+
}, model)
|
|
3940
4137
|
});
|
|
3941
4138
|
}
|
|
3942
4139
|
files.push({
|
package/dist/emit-client.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
import type { Table } from "./introspect";
|
|
2
|
-
|
|
1
|
+
import type { Table, Model } from "./introspect";
|
|
2
|
+
import type { Graph } from "./rel-classify";
|
|
3
|
+
export declare function emitClient(table: Table, graph: Graph, opts: {
|
|
4
|
+
useJsExtensions?: boolean;
|
|
5
|
+
includeMethodsDepth?: number;
|
|
6
|
+
skipJunctionTables?: boolean;
|
|
7
|
+
}, model?: Model): string;
|
|
3
8
|
export declare function emitClientIndex(tables: Table[], useJsExtensions?: boolean): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Table } from "./introspect";
|
|
2
|
+
import type { Graph } from "./rel-classify";
|
|
3
|
+
export type IncludeMethod = {
|
|
4
|
+
name: string;
|
|
5
|
+
path: string[];
|
|
6
|
+
isMany: boolean[];
|
|
7
|
+
targets: string[];
|
|
8
|
+
returnType: string;
|
|
9
|
+
includeSpec: any;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Generate all include methods for a table
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateIncludeMethods(table: Table, graph: Graph, opts: {
|
|
15
|
+
maxDepth: number;
|
|
16
|
+
skipJunctionTables: boolean;
|
|
17
|
+
}, allTables?: Table[]): IncludeMethod[];
|
package/dist/index.js
CHANGED
|
@@ -1651,18 +1651,214 @@ ${hasAuth ? `
|
|
|
1651
1651
|
|
|
1652
1652
|
// src/emit-client.ts
|
|
1653
1653
|
init_utils();
|
|
1654
|
-
|
|
1654
|
+
|
|
1655
|
+
// src/emit-include-methods.ts
|
|
1656
|
+
init_utils();
|
|
1657
|
+
function isJunctionTable(table) {
|
|
1658
|
+
if (!table.name.includes("_"))
|
|
1659
|
+
return false;
|
|
1660
|
+
const fkColumns = new Set(table.fks.flatMap((fk) => fk.from));
|
|
1661
|
+
const nonPkColumns = table.columns.filter((c) => !table.pk.includes(c.name));
|
|
1662
|
+
return nonPkColumns.every((c) => fkColumns.has(c.name));
|
|
1663
|
+
}
|
|
1664
|
+
function pathToMethodSuffix(path) {
|
|
1665
|
+
return "With" + path.map((p) => pascal(p)).join("And");
|
|
1666
|
+
}
|
|
1667
|
+
function buildReturnType(baseTable, path, isMany, targets, graph) {
|
|
1668
|
+
const BaseType = `Select${pascal(baseTable)}`;
|
|
1669
|
+
if (path.length === 0)
|
|
1670
|
+
return BaseType;
|
|
1671
|
+
let type = BaseType;
|
|
1672
|
+
let currentTable = baseTable;
|
|
1673
|
+
const parts = [];
|
|
1674
|
+
for (let i = 0;i < path.length; i++) {
|
|
1675
|
+
const key = path[i];
|
|
1676
|
+
const target = targets[i];
|
|
1677
|
+
if (!key || !target)
|
|
1678
|
+
continue;
|
|
1679
|
+
const targetType = `Select${pascal(target)}`;
|
|
1680
|
+
if (i === 0) {
|
|
1681
|
+
parts.push(`${key}: ${isMany[i] ? `${targetType}[]` : targetType}`);
|
|
1682
|
+
} else {
|
|
1683
|
+
let nestedType = targetType;
|
|
1684
|
+
for (let j = i;j < path.length; j++) {
|
|
1685
|
+
if (j > i) {
|
|
1686
|
+
const nestedKey = path[j];
|
|
1687
|
+
const nestedTarget = targets[j];
|
|
1688
|
+
if (!nestedKey || !nestedTarget)
|
|
1689
|
+
continue;
|
|
1690
|
+
const nestedTargetType = `Select${pascal(nestedTarget)}`;
|
|
1691
|
+
nestedType = `${nestedType} & { ${nestedKey}: ${isMany[j] ? `${nestedTargetType}[]` : nestedTargetType} }`;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
const prevKey = path[i - 1];
|
|
1695
|
+
const prevTarget = targets[i - 1];
|
|
1696
|
+
if (prevKey && prevTarget) {
|
|
1697
|
+
parts[parts.length - 1] = `${prevKey}: ${isMany[i - 1] ? `(Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} })[]` : `Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} }`}`;
|
|
1698
|
+
}
|
|
1699
|
+
break;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
return `${type} & { ${parts.join("; ")} }`;
|
|
1703
|
+
}
|
|
1704
|
+
function buildIncludeSpec(path) {
|
|
1705
|
+
if (path.length === 0)
|
|
1706
|
+
return {};
|
|
1707
|
+
if (path.length === 1)
|
|
1708
|
+
return { [path[0]]: true };
|
|
1709
|
+
let spec = true;
|
|
1710
|
+
for (let i = path.length - 1;i > 0; i--) {
|
|
1711
|
+
const key = path[i];
|
|
1712
|
+
if (!key)
|
|
1713
|
+
continue;
|
|
1714
|
+
spec = { [key]: spec };
|
|
1715
|
+
}
|
|
1716
|
+
const rootKey = path[0];
|
|
1717
|
+
return rootKey ? { [rootKey]: spec } : {};
|
|
1718
|
+
}
|
|
1719
|
+
function generateIncludeMethods(table, graph, opts, allTables) {
|
|
1720
|
+
const methods = [];
|
|
1721
|
+
const baseTableName = table.name;
|
|
1722
|
+
if (opts.skipJunctionTables && isJunctionTable(table)) {
|
|
1723
|
+
return methods;
|
|
1724
|
+
}
|
|
1725
|
+
const edges = graph[baseTableName] || {};
|
|
1726
|
+
function explore(currentTable, path, isMany, targets, visited, depth) {
|
|
1727
|
+
if (depth > opts.maxDepth)
|
|
1728
|
+
return;
|
|
1729
|
+
const currentEdges = graph[currentTable] || {};
|
|
1730
|
+
for (const [key, edge] of Object.entries(currentEdges)) {
|
|
1731
|
+
if (visited.has(edge.target))
|
|
1732
|
+
continue;
|
|
1733
|
+
if (opts.skipJunctionTables && allTables) {
|
|
1734
|
+
const targetTable = allTables.find((t) => t.name === edge.target);
|
|
1735
|
+
if (targetTable && isJunctionTable(targetTable)) {
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
const newPath = [...path, key];
|
|
1740
|
+
const newIsMany = [...isMany, edge.kind === "many"];
|
|
1741
|
+
const newTargets = [...targets, edge.target];
|
|
1742
|
+
const methodSuffix = pathToMethodSuffix(newPath);
|
|
1743
|
+
methods.push({
|
|
1744
|
+
name: `list${methodSuffix}`,
|
|
1745
|
+
path: newPath,
|
|
1746
|
+
isMany: newIsMany,
|
|
1747
|
+
targets: newTargets,
|
|
1748
|
+
returnType: `(${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]`,
|
|
1749
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
1750
|
+
});
|
|
1751
|
+
methods.push({
|
|
1752
|
+
name: `getByPk${methodSuffix}`,
|
|
1753
|
+
path: newPath,
|
|
1754
|
+
isMany: newIsMany,
|
|
1755
|
+
targets: newTargets,
|
|
1756
|
+
returnType: `${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)} | null`,
|
|
1757
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
1758
|
+
});
|
|
1759
|
+
explore(edge.target, newPath, newIsMany, newTargets, new Set([...visited, edge.target]), depth + 1);
|
|
1760
|
+
}
|
|
1761
|
+
if (depth === 1 && Object.keys(currentEdges).length > 1 && Object.keys(currentEdges).length <= 3) {
|
|
1762
|
+
const edgeEntries = Object.entries(currentEdges);
|
|
1763
|
+
if (edgeEntries.length >= 2) {
|
|
1764
|
+
for (let i = 0;i < edgeEntries.length - 1; i++) {
|
|
1765
|
+
for (let j = i + 1;j < edgeEntries.length; j++) {
|
|
1766
|
+
const entry1 = edgeEntries[i];
|
|
1767
|
+
const entry2 = edgeEntries[j];
|
|
1768
|
+
if (!entry1 || !entry2)
|
|
1769
|
+
continue;
|
|
1770
|
+
const [key1, edge1] = entry1;
|
|
1771
|
+
const [key2, edge2] = entry2;
|
|
1772
|
+
if (opts.skipJunctionTables && (edge1.target.includes("_") || edge2.target.includes("_"))) {
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
const combinedPath = [key1, key2];
|
|
1776
|
+
const combinedSuffix = `With${pascal(key1)}And${pascal(key2)}`;
|
|
1777
|
+
const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}`}`;
|
|
1778
|
+
const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}`}`;
|
|
1779
|
+
methods.push({
|
|
1780
|
+
name: `list${combinedSuffix}`,
|
|
1781
|
+
path: combinedPath,
|
|
1782
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
1783
|
+
targets: [edge1.target, edge2.target],
|
|
1784
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]`,
|
|
1785
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
1786
|
+
});
|
|
1787
|
+
methods.push({
|
|
1788
|
+
name: `getByPk${combinedSuffix}`,
|
|
1789
|
+
path: combinedPath,
|
|
1790
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
1791
|
+
targets: [edge1.target, edge2.target],
|
|
1792
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} }) | null`,
|
|
1793
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
explore(baseTableName, [], [], [], new Set([baseTableName]), 1);
|
|
1801
|
+
return methods;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// src/emit-client.ts
|
|
1805
|
+
function emitClient(table, graph, opts, model) {
|
|
1655
1806
|
const Type = pascal(table.name);
|
|
1656
|
-
const ext = useJsExtensions ? ".js" : "";
|
|
1807
|
+
const ext = opts.useJsExtensions ? ".js" : "";
|
|
1657
1808
|
const pkCols = Array.isArray(table.pk) ? table.pk : table.pk ? [table.pk] : [];
|
|
1658
1809
|
const safePk = pkCols.length ? pkCols : ["id"];
|
|
1659
1810
|
const hasCompositePk = safePk.length > 1;
|
|
1660
1811
|
const pkType = hasCompositePk ? `{ ${safePk.map((c) => `${c}: string`).join("; ")} }` : `string`;
|
|
1661
1812
|
const pkPathExpr = hasCompositePk ? safePk.map((c) => `pk.${c}`).join(` + "/" + `) : `pk`;
|
|
1813
|
+
const allTables = model ? Object.values(model.tables) : undefined;
|
|
1814
|
+
const includeMethods = generateIncludeMethods(table, graph, {
|
|
1815
|
+
maxDepth: opts.includeMethodsDepth ?? 2,
|
|
1816
|
+
skipJunctionTables: opts.skipJunctionTables ?? true
|
|
1817
|
+
}, allTables);
|
|
1818
|
+
const importedTypes = new Set;
|
|
1819
|
+
importedTypes.add(table.name);
|
|
1820
|
+
for (const method of includeMethods) {
|
|
1821
|
+
for (const target of method.targets) {
|
|
1822
|
+
importedTypes.add(target);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
|
|
1826
|
+
const otherTableImports = [];
|
|
1827
|
+
for (const target of Array.from(importedTypes)) {
|
|
1828
|
+
if (target !== table.name) {
|
|
1829
|
+
otherTableImports.push(`import type { Select${pascal(target)} } from "./types/${target}${ext}";`);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
let includeMethodsCode = "";
|
|
1833
|
+
for (const method of includeMethods) {
|
|
1834
|
+
const isGetByPk = method.name.startsWith("getByPk");
|
|
1835
|
+
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: any; orderBy?: string; order?: "asc" | "desc"; }, "include">`;
|
|
1836
|
+
if (isGetByPk) {
|
|
1837
|
+
const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
|
|
1838
|
+
const baseReturnType = method.returnType.replace(" | null", "");
|
|
1839
|
+
includeMethodsCode += `
|
|
1840
|
+
async ${method.name}(pk: ${pkType}): Promise<${method.returnType}> {
|
|
1841
|
+
const results = await this.post<${baseReturnType}[]>(\`\${this.resource}/list\`, {
|
|
1842
|
+
where: ${pkWhere},
|
|
1843
|
+
include: ${JSON.stringify(method.includeSpec)},
|
|
1844
|
+
limit: 1
|
|
1845
|
+
});
|
|
1846
|
+
return (results[0] as ${baseReturnType}) ?? null;
|
|
1847
|
+
}
|
|
1848
|
+
`;
|
|
1849
|
+
} else {
|
|
1850
|
+
includeMethodsCode += `
|
|
1851
|
+
async ${method.name}(${baseParams}): Promise<${method.returnType}> {
|
|
1852
|
+
return this.post<${method.returnType}>(\`\${this.resource}/list\`, { ...params, include: ${JSON.stringify(method.includeSpec)} });
|
|
1853
|
+
}
|
|
1854
|
+
`;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1662
1857
|
return `/* Generated. Do not edit. */
|
|
1663
1858
|
import { BaseClient } from "./base-client${ext}";
|
|
1664
|
-
|
|
1665
|
-
|
|
1859
|
+
${typeImports}
|
|
1860
|
+
${otherTableImports.join(`
|
|
1861
|
+
`)}
|
|
1666
1862
|
|
|
1667
1863
|
/**
|
|
1668
1864
|
* Client for ${table.name} table operations
|
|
@@ -1680,7 +1876,6 @@ export class ${Type}Client extends BaseClient {
|
|
|
1680
1876
|
}
|
|
1681
1877
|
|
|
1682
1878
|
async list(params?: {
|
|
1683
|
-
include?: ${Type}IncludeSpec;
|
|
1684
1879
|
limit?: number;
|
|
1685
1880
|
offset?: number;
|
|
1686
1881
|
where?: any;
|
|
@@ -1699,7 +1894,7 @@ export class ${Type}Client extends BaseClient {
|
|
|
1699
1894
|
const path = ${pkPathExpr};
|
|
1700
1895
|
return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
1701
1896
|
}
|
|
1702
|
-
}
|
|
1897
|
+
${includeMethodsCode}}
|
|
1703
1898
|
`;
|
|
1704
1899
|
}
|
|
1705
1900
|
function emitClientIndex(tables, useJsExtensions) {
|
|
@@ -1743,8 +1938,6 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
|
|
|
1743
1938
|
|
|
1744
1939
|
`;
|
|
1745
1940
|
out += `export { BaseClient } from "./base-client${ext}";
|
|
1746
|
-
`;
|
|
1747
|
-
out += `export * from "./include-spec${ext}";
|
|
1748
1941
|
`;
|
|
1749
1942
|
out += `
|
|
1750
1943
|
// Zod schemas for form validation
|
|
@@ -2821,12 +3014,12 @@ function emitTableTest(table, model, clientPath, framework = "vitest") {
|
|
|
2821
3014
|
const Type = pascal(table.name);
|
|
2822
3015
|
const tableName = table.name;
|
|
2823
3016
|
const imports = getFrameworkImports(framework);
|
|
2824
|
-
const
|
|
3017
|
+
const isJunctionTable2 = table.pk.length > 1;
|
|
2825
3018
|
const hasForeignKeys = table.fks.length > 0;
|
|
2826
3019
|
const foreignKeySetup = hasForeignKeys ? generateForeignKeySetup(table, model, clientPath) : null;
|
|
2827
3020
|
const sampleData = generateSampleDataFromSchema(table, hasForeignKeys);
|
|
2828
3021
|
const updateData = generateUpdateDataFromSchema(table);
|
|
2829
|
-
if (
|
|
3022
|
+
if (isJunctionTable2) {
|
|
2830
3023
|
return `${imports}
|
|
2831
3024
|
import { SDK } from '${clientPath}';
|
|
2832
3025
|
import type { Insert${Type}, Update${Type}, Select${Type} } from '${clientPath}/types/${tableName}';
|
|
@@ -3630,11 +3823,11 @@ async function generate(configPath) {
|
|
|
3630
3823
|
files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
|
|
3631
3824
|
files.push({
|
|
3632
3825
|
path: join(serverDir, "include-builder.ts"),
|
|
3633
|
-
content: emitIncludeBuilder(graph, cfg.
|
|
3826
|
+
content: emitIncludeBuilder(graph, cfg.includeMethodsDepth || 2)
|
|
3634
3827
|
});
|
|
3635
3828
|
files.push({
|
|
3636
3829
|
path: join(serverDir, "include-loader.ts"),
|
|
3637
|
-
content: emitIncludeLoader(graph, model, cfg.
|
|
3830
|
+
content: emitIncludeLoader(graph, model, cfg.includeMethodsDepth || 2, cfg.useJsExtensions)
|
|
3638
3831
|
});
|
|
3639
3832
|
files.push({ path: join(serverDir, "logger.ts"), content: emitLogger() });
|
|
3640
3833
|
if (normalizedAuth?.strategy && normalizedAuth.strategy !== "none") {
|
|
@@ -3660,7 +3853,7 @@ async function generate(configPath) {
|
|
|
3660
3853
|
if (serverFramework === "hono") {
|
|
3661
3854
|
routeContent = emitHonoRoutes(table, graph, {
|
|
3662
3855
|
softDeleteColumn: cfg.softDeleteColumn || null,
|
|
3663
|
-
includeDepthLimit: cfg.
|
|
3856
|
+
includeDepthLimit: cfg.includeMethodsDepth || 2,
|
|
3664
3857
|
authStrategy: normalizedAuth?.strategy,
|
|
3665
3858
|
useJsExtensions: cfg.useJsExtensions
|
|
3666
3859
|
});
|
|
@@ -3673,7 +3866,11 @@ async function generate(configPath) {
|
|
|
3673
3866
|
});
|
|
3674
3867
|
files.push({
|
|
3675
3868
|
path: join(clientDir, `${table.name}.ts`),
|
|
3676
|
-
content: emitClient(table,
|
|
3869
|
+
content: emitClient(table, graph, {
|
|
3870
|
+
useJsExtensions: cfg.useJsExtensionsClient,
|
|
3871
|
+
includeMethodsDepth: cfg.includeMethodsDepth ?? 2,
|
|
3872
|
+
skipJunctionTables: cfg.skipJunctionTables ?? true
|
|
3873
|
+
}, model)
|
|
3677
3874
|
});
|
|
3678
3875
|
}
|
|
3679
3876
|
files.push({
|
package/dist/types.d.ts
CHANGED
|
@@ -24,7 +24,9 @@ export interface Config {
|
|
|
24
24
|
outServer?: string;
|
|
25
25
|
outClient?: string;
|
|
26
26
|
softDeleteColumn?: string | null;
|
|
27
|
-
|
|
27
|
+
dateType?: "date" | "string";
|
|
28
|
+
includeMethodsDepth?: number;
|
|
29
|
+
skipJunctionTables?: boolean;
|
|
28
30
|
serverFramework?: "hono" | "express" | "fastify";
|
|
29
31
|
auth?: AuthConfigInput;
|
|
30
32
|
pull?: PullConfig;
|