postgresdk 0.7.4 → 0.8.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 +219 -13
- package/dist/emit-client.d.ts +6 -1
- package/dist/emit-include-methods.d.ts +17 -0
- package/dist/index.js +219 -13
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1668,7 +1668,15 @@ function emitZod(table, opts) {
|
|
|
1668
1668
|
return `z.array(${zFor(pg.slice(1))})`;
|
|
1669
1669
|
return `z.string()`;
|
|
1670
1670
|
};
|
|
1671
|
-
const
|
|
1671
|
+
const selectFields = table.columns.map((c) => {
|
|
1672
|
+
let z = zFor(c.pgType);
|
|
1673
|
+
if (c.nullable) {
|
|
1674
|
+
z += `.nullable()`;
|
|
1675
|
+
}
|
|
1676
|
+
return ` ${c.name}: ${z}`;
|
|
1677
|
+
}).join(`,
|
|
1678
|
+
`);
|
|
1679
|
+
const insertFields = table.columns.map((c) => {
|
|
1672
1680
|
let z = zFor(c.pgType);
|
|
1673
1681
|
if (c.nullable) {
|
|
1674
1682
|
z += `.nullish()`;
|
|
@@ -1680,8 +1688,12 @@ function emitZod(table, opts) {
|
|
|
1680
1688
|
`);
|
|
1681
1689
|
return `import { z } from "zod";
|
|
1682
1690
|
|
|
1691
|
+
export const Select${Type}Schema = z.object({
|
|
1692
|
+
${selectFields}
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1683
1695
|
export const Insert${Type}Schema = z.object({
|
|
1684
|
-
${
|
|
1696
|
+
${insertFields}
|
|
1685
1697
|
});
|
|
1686
1698
|
|
|
1687
1699
|
export const Update${Type}Schema = Insert${Type}Schema.partial();
|
|
@@ -1902,18 +1914,211 @@ ${hasAuth ? `
|
|
|
1902
1914
|
|
|
1903
1915
|
// src/emit-client.ts
|
|
1904
1916
|
init_utils();
|
|
1905
|
-
|
|
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) {
|
|
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
|
+
const targetTable = Object.values(graph).find((t) => Object.values(t).some((e) => e.target === edge.target));
|
|
1997
|
+
if (opts.skipJunctionTables && edge.target.includes("_")) {
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
const newPath = [...path, key];
|
|
2001
|
+
const newIsMany = [...isMany, edge.kind === "many"];
|
|
2002
|
+
const newTargets = [...targets, edge.target];
|
|
2003
|
+
const methodSuffix = pathToMethodSuffix(newPath);
|
|
2004
|
+
methods.push({
|
|
2005
|
+
name: `list${methodSuffix}`,
|
|
2006
|
+
path: newPath,
|
|
2007
|
+
isMany: newIsMany,
|
|
2008
|
+
targets: newTargets,
|
|
2009
|
+
returnType: `(${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]`,
|
|
2010
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
2011
|
+
});
|
|
2012
|
+
methods.push({
|
|
2013
|
+
name: `getByPk${methodSuffix}`,
|
|
2014
|
+
path: newPath,
|
|
2015
|
+
isMany: newIsMany,
|
|
2016
|
+
targets: newTargets,
|
|
2017
|
+
returnType: `${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)} | null`,
|
|
2018
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
2019
|
+
});
|
|
2020
|
+
explore(edge.target, newPath, newIsMany, newTargets, new Set([...visited, edge.target]), depth + 1);
|
|
2021
|
+
}
|
|
2022
|
+
if (depth === 1 && Object.keys(currentEdges).length > 1 && Object.keys(currentEdges).length <= 3) {
|
|
2023
|
+
const edgeEntries = Object.entries(currentEdges);
|
|
2024
|
+
if (edgeEntries.length >= 2) {
|
|
2025
|
+
for (let i = 0;i < edgeEntries.length - 1; i++) {
|
|
2026
|
+
for (let j = i + 1;j < edgeEntries.length; j++) {
|
|
2027
|
+
const entry1 = edgeEntries[i];
|
|
2028
|
+
const entry2 = edgeEntries[j];
|
|
2029
|
+
if (!entry1 || !entry2)
|
|
2030
|
+
continue;
|
|
2031
|
+
const [key1, edge1] = entry1;
|
|
2032
|
+
const [key2, edge2] = entry2;
|
|
2033
|
+
if (opts.skipJunctionTables && (edge1.target.includes("_") || edge2.target.includes("_"))) {
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
const combinedPath = [key1, key2];
|
|
2037
|
+
const combinedSuffix = `With${pascal(key1)}And${pascal(key2)}`;
|
|
2038
|
+
const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}`}`;
|
|
2039
|
+
const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}`}`;
|
|
2040
|
+
methods.push({
|
|
2041
|
+
name: `list${combinedSuffix}`,
|
|
2042
|
+
path: combinedPath,
|
|
2043
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
2044
|
+
targets: [edge1.target, edge2.target],
|
|
2045
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]`,
|
|
2046
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
2047
|
+
});
|
|
2048
|
+
methods.push({
|
|
2049
|
+
name: `getByPk${combinedSuffix}`,
|
|
2050
|
+
path: combinedPath,
|
|
2051
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
2052
|
+
targets: [edge1.target, edge2.target],
|
|
2053
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} }) | null`,
|
|
2054
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
explore(baseTableName, [], [], [], new Set([baseTableName]), 1);
|
|
2062
|
+
return methods;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
// src/emit-client.ts
|
|
2066
|
+
function emitClient(table, graph, opts) {
|
|
1906
2067
|
const Type = pascal(table.name);
|
|
1907
|
-
const ext = useJsExtensions ? ".js" : "";
|
|
2068
|
+
const ext = opts.useJsExtensions ? ".js" : "";
|
|
1908
2069
|
const pkCols = Array.isArray(table.pk) ? table.pk : table.pk ? [table.pk] : [];
|
|
1909
2070
|
const safePk = pkCols.length ? pkCols : ["id"];
|
|
1910
2071
|
const hasCompositePk = safePk.length > 1;
|
|
1911
2072
|
const pkType = hasCompositePk ? `{ ${safePk.map((c) => `${c}: string`).join("; ")} }` : `string`;
|
|
1912
2073
|
const pkPathExpr = hasCompositePk ? safePk.map((c) => `pk.${c}`).join(` + "/" + `) : `pk`;
|
|
2074
|
+
const includeMethods = generateIncludeMethods(table, graph, {
|
|
2075
|
+
maxDepth: opts.includeMethodsDepth ?? 2,
|
|
2076
|
+
skipJunctionTables: opts.skipJunctionTables ?? true
|
|
2077
|
+
});
|
|
2078
|
+
const importedTypes = new Set;
|
|
2079
|
+
importedTypes.add(table.name);
|
|
2080
|
+
for (const method of includeMethods) {
|
|
2081
|
+
for (const target of method.targets) {
|
|
2082
|
+
importedTypes.add(target);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
|
|
2086
|
+
const otherTableImports = [];
|
|
2087
|
+
for (const target of importedTypes) {
|
|
2088
|
+
if (target !== table.name) {
|
|
2089
|
+
otherTableImports.push(`import type { Select${pascal(target)} } from "./types/${target}${ext}";`);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
let includeMethodsCode = "";
|
|
2093
|
+
for (const method of includeMethods) {
|
|
2094
|
+
const isGetByPk = method.name.startsWith("getByPk");
|
|
2095
|
+
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: any; orderBy?: string; order?: "asc" | "desc"; }, "include">`;
|
|
2096
|
+
if (isGetByPk) {
|
|
2097
|
+
const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
|
|
2098
|
+
const baseReturnType = method.returnType.replace(" | null", "");
|
|
2099
|
+
includeMethodsCode += `
|
|
2100
|
+
async ${method.name}(pk: ${pkType}): Promise<${method.returnType}> {
|
|
2101
|
+
const results = await this.post<${baseReturnType}[]>(\`\${this.resource}/list\`, {
|
|
2102
|
+
where: ${pkWhere},
|
|
2103
|
+
include: ${JSON.stringify(method.includeSpec)},
|
|
2104
|
+
limit: 1
|
|
2105
|
+
});
|
|
2106
|
+
return (results[0] as ${baseReturnType}) ?? null;
|
|
2107
|
+
}
|
|
2108
|
+
`;
|
|
2109
|
+
} else {
|
|
2110
|
+
includeMethodsCode += `
|
|
2111
|
+
async ${method.name}(${baseParams}): Promise<${method.returnType}> {
|
|
2112
|
+
return this.post<${method.returnType}>(\`\${this.resource}/list\`, { ...params, include: ${JSON.stringify(method.includeSpec)} });
|
|
2113
|
+
}
|
|
2114
|
+
`;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
1913
2117
|
return `/* Generated. Do not edit. */
|
|
1914
2118
|
import { BaseClient } from "./base-client${ext}";
|
|
1915
|
-
|
|
1916
|
-
|
|
2119
|
+
${typeImports}
|
|
2120
|
+
${otherTableImports.join(`
|
|
2121
|
+
`)}
|
|
1917
2122
|
|
|
1918
2123
|
/**
|
|
1919
2124
|
* Client for ${table.name} table operations
|
|
@@ -1931,7 +2136,6 @@ export class ${Type}Client extends BaseClient {
|
|
|
1931
2136
|
}
|
|
1932
2137
|
|
|
1933
2138
|
async list(params?: {
|
|
1934
|
-
include?: ${Type}IncludeSpec;
|
|
1935
2139
|
limit?: number;
|
|
1936
2140
|
offset?: number;
|
|
1937
2141
|
where?: any;
|
|
@@ -1950,7 +2154,7 @@ export class ${Type}Client extends BaseClient {
|
|
|
1950
2154
|
const path = ${pkPathExpr};
|
|
1951
2155
|
return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
1952
2156
|
}
|
|
1953
|
-
}
|
|
2157
|
+
${includeMethodsCode}}
|
|
1954
2158
|
`;
|
|
1955
2159
|
}
|
|
1956
2160
|
function emitClientIndex(tables, useJsExtensions) {
|
|
@@ -1994,8 +2198,6 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
|
|
|
1994
2198
|
|
|
1995
2199
|
`;
|
|
1996
2200
|
out += `export { BaseClient } from "./base-client${ext}";
|
|
1997
|
-
`;
|
|
1998
|
-
out += `export * from "./include-spec${ext}";
|
|
1999
2201
|
`;
|
|
2000
2202
|
out += `
|
|
2001
2203
|
// Zod schemas for form validation
|
|
@@ -3072,12 +3274,12 @@ function emitTableTest(table, model, clientPath, framework = "vitest") {
|
|
|
3072
3274
|
const Type = pascal(table.name);
|
|
3073
3275
|
const tableName = table.name;
|
|
3074
3276
|
const imports = getFrameworkImports(framework);
|
|
3075
|
-
const
|
|
3277
|
+
const isJunctionTable2 = table.pk.length > 1;
|
|
3076
3278
|
const hasForeignKeys = table.fks.length > 0;
|
|
3077
3279
|
const foreignKeySetup = hasForeignKeys ? generateForeignKeySetup(table, model, clientPath) : null;
|
|
3078
3280
|
const sampleData = generateSampleDataFromSchema(table, hasForeignKeys);
|
|
3079
3281
|
const updateData = generateUpdateDataFromSchema(table);
|
|
3080
|
-
if (
|
|
3282
|
+
if (isJunctionTable2) {
|
|
3081
3283
|
return `${imports}
|
|
3082
3284
|
import { SDK } from '${clientPath}';
|
|
3083
3285
|
import type { Insert${Type}, Update${Type}, Select${Type} } from '${clientPath}/types/${tableName}';
|
|
@@ -3924,7 +4126,11 @@ async function generate(configPath) {
|
|
|
3924
4126
|
});
|
|
3925
4127
|
files.push({
|
|
3926
4128
|
path: join(clientDir, `${table.name}.ts`),
|
|
3927
|
-
content: emitClient(table,
|
|
4129
|
+
content: emitClient(table, graph, {
|
|
4130
|
+
useJsExtensions: cfg.useJsExtensionsClient,
|
|
4131
|
+
includeMethodsDepth: cfg.includeMethodsDepth ?? 2,
|
|
4132
|
+
skipJunctionTables: cfg.skipJunctionTables ?? true
|
|
4133
|
+
})
|
|
3928
4134
|
});
|
|
3929
4135
|
}
|
|
3930
4136
|
files.push({
|
package/dist/emit-client.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { Table } from "./introspect";
|
|
2
|
-
|
|
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
|
+
}): 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
|
+
}): IncludeMethod[];
|
package/dist/index.js
CHANGED
|
@@ -1405,7 +1405,15 @@ function emitZod(table, opts) {
|
|
|
1405
1405
|
return `z.array(${zFor(pg.slice(1))})`;
|
|
1406
1406
|
return `z.string()`;
|
|
1407
1407
|
};
|
|
1408
|
-
const
|
|
1408
|
+
const selectFields = table.columns.map((c) => {
|
|
1409
|
+
let z = zFor(c.pgType);
|
|
1410
|
+
if (c.nullable) {
|
|
1411
|
+
z += `.nullable()`;
|
|
1412
|
+
}
|
|
1413
|
+
return ` ${c.name}: ${z}`;
|
|
1414
|
+
}).join(`,
|
|
1415
|
+
`);
|
|
1416
|
+
const insertFields = table.columns.map((c) => {
|
|
1409
1417
|
let z = zFor(c.pgType);
|
|
1410
1418
|
if (c.nullable) {
|
|
1411
1419
|
z += `.nullish()`;
|
|
@@ -1417,8 +1425,12 @@ function emitZod(table, opts) {
|
|
|
1417
1425
|
`);
|
|
1418
1426
|
return `import { z } from "zod";
|
|
1419
1427
|
|
|
1428
|
+
export const Select${Type}Schema = z.object({
|
|
1429
|
+
${selectFields}
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1420
1432
|
export const Insert${Type}Schema = z.object({
|
|
1421
|
-
${
|
|
1433
|
+
${insertFields}
|
|
1422
1434
|
});
|
|
1423
1435
|
|
|
1424
1436
|
export const Update${Type}Schema = Insert${Type}Schema.partial();
|
|
@@ -1639,18 +1651,211 @@ ${hasAuth ? `
|
|
|
1639
1651
|
|
|
1640
1652
|
// src/emit-client.ts
|
|
1641
1653
|
init_utils();
|
|
1642
|
-
|
|
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) {
|
|
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
|
+
const targetTable = Object.values(graph).find((t) => Object.values(t).some((e) => e.target === edge.target));
|
|
1734
|
+
if (opts.skipJunctionTables && edge.target.includes("_")) {
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
const newPath = [...path, key];
|
|
1738
|
+
const newIsMany = [...isMany, edge.kind === "many"];
|
|
1739
|
+
const newTargets = [...targets, edge.target];
|
|
1740
|
+
const methodSuffix = pathToMethodSuffix(newPath);
|
|
1741
|
+
methods.push({
|
|
1742
|
+
name: `list${methodSuffix}`,
|
|
1743
|
+
path: newPath,
|
|
1744
|
+
isMany: newIsMany,
|
|
1745
|
+
targets: newTargets,
|
|
1746
|
+
returnType: `(${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]`,
|
|
1747
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
1748
|
+
});
|
|
1749
|
+
methods.push({
|
|
1750
|
+
name: `getByPk${methodSuffix}`,
|
|
1751
|
+
path: newPath,
|
|
1752
|
+
isMany: newIsMany,
|
|
1753
|
+
targets: newTargets,
|
|
1754
|
+
returnType: `${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)} | null`,
|
|
1755
|
+
includeSpec: buildIncludeSpec(newPath)
|
|
1756
|
+
});
|
|
1757
|
+
explore(edge.target, newPath, newIsMany, newTargets, new Set([...visited, edge.target]), depth + 1);
|
|
1758
|
+
}
|
|
1759
|
+
if (depth === 1 && Object.keys(currentEdges).length > 1 && Object.keys(currentEdges).length <= 3) {
|
|
1760
|
+
const edgeEntries = Object.entries(currentEdges);
|
|
1761
|
+
if (edgeEntries.length >= 2) {
|
|
1762
|
+
for (let i = 0;i < edgeEntries.length - 1; i++) {
|
|
1763
|
+
for (let j = i + 1;j < edgeEntries.length; j++) {
|
|
1764
|
+
const entry1 = edgeEntries[i];
|
|
1765
|
+
const entry2 = edgeEntries[j];
|
|
1766
|
+
if (!entry1 || !entry2)
|
|
1767
|
+
continue;
|
|
1768
|
+
const [key1, edge1] = entry1;
|
|
1769
|
+
const [key2, edge2] = entry2;
|
|
1770
|
+
if (opts.skipJunctionTables && (edge1.target.includes("_") || edge2.target.includes("_"))) {
|
|
1771
|
+
continue;
|
|
1772
|
+
}
|
|
1773
|
+
const combinedPath = [key1, key2];
|
|
1774
|
+
const combinedSuffix = `With${pascal(key1)}And${pascal(key2)}`;
|
|
1775
|
+
const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}`}`;
|
|
1776
|
+
const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}`}`;
|
|
1777
|
+
methods.push({
|
|
1778
|
+
name: `list${combinedSuffix}`,
|
|
1779
|
+
path: combinedPath,
|
|
1780
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
1781
|
+
targets: [edge1.target, edge2.target],
|
|
1782
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]`,
|
|
1783
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
1784
|
+
});
|
|
1785
|
+
methods.push({
|
|
1786
|
+
name: `getByPk${combinedSuffix}`,
|
|
1787
|
+
path: combinedPath,
|
|
1788
|
+
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
1789
|
+
targets: [edge1.target, edge2.target],
|
|
1790
|
+
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} }) | null`,
|
|
1791
|
+
includeSpec: { [key1]: true, [key2]: true }
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
explore(baseTableName, [], [], [], new Set([baseTableName]), 1);
|
|
1799
|
+
return methods;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// src/emit-client.ts
|
|
1803
|
+
function emitClient(table, graph, opts) {
|
|
1643
1804
|
const Type = pascal(table.name);
|
|
1644
|
-
const ext = useJsExtensions ? ".js" : "";
|
|
1805
|
+
const ext = opts.useJsExtensions ? ".js" : "";
|
|
1645
1806
|
const pkCols = Array.isArray(table.pk) ? table.pk : table.pk ? [table.pk] : [];
|
|
1646
1807
|
const safePk = pkCols.length ? pkCols : ["id"];
|
|
1647
1808
|
const hasCompositePk = safePk.length > 1;
|
|
1648
1809
|
const pkType = hasCompositePk ? `{ ${safePk.map((c) => `${c}: string`).join("; ")} }` : `string`;
|
|
1649
1810
|
const pkPathExpr = hasCompositePk ? safePk.map((c) => `pk.${c}`).join(` + "/" + `) : `pk`;
|
|
1811
|
+
const includeMethods = generateIncludeMethods(table, graph, {
|
|
1812
|
+
maxDepth: opts.includeMethodsDepth ?? 2,
|
|
1813
|
+
skipJunctionTables: opts.skipJunctionTables ?? true
|
|
1814
|
+
});
|
|
1815
|
+
const importedTypes = new Set;
|
|
1816
|
+
importedTypes.add(table.name);
|
|
1817
|
+
for (const method of includeMethods) {
|
|
1818
|
+
for (const target of method.targets) {
|
|
1819
|
+
importedTypes.add(target);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
|
|
1823
|
+
const otherTableImports = [];
|
|
1824
|
+
for (const target of importedTypes) {
|
|
1825
|
+
if (target !== table.name) {
|
|
1826
|
+
otherTableImports.push(`import type { Select${pascal(target)} } from "./types/${target}${ext}";`);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
let includeMethodsCode = "";
|
|
1830
|
+
for (const method of includeMethods) {
|
|
1831
|
+
const isGetByPk = method.name.startsWith("getByPk");
|
|
1832
|
+
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: any; orderBy?: string; order?: "asc" | "desc"; }, "include">`;
|
|
1833
|
+
if (isGetByPk) {
|
|
1834
|
+
const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
|
|
1835
|
+
const baseReturnType = method.returnType.replace(" | null", "");
|
|
1836
|
+
includeMethodsCode += `
|
|
1837
|
+
async ${method.name}(pk: ${pkType}): Promise<${method.returnType}> {
|
|
1838
|
+
const results = await this.post<${baseReturnType}[]>(\`\${this.resource}/list\`, {
|
|
1839
|
+
where: ${pkWhere},
|
|
1840
|
+
include: ${JSON.stringify(method.includeSpec)},
|
|
1841
|
+
limit: 1
|
|
1842
|
+
});
|
|
1843
|
+
return (results[0] as ${baseReturnType}) ?? null;
|
|
1844
|
+
}
|
|
1845
|
+
`;
|
|
1846
|
+
} else {
|
|
1847
|
+
includeMethodsCode += `
|
|
1848
|
+
async ${method.name}(${baseParams}): Promise<${method.returnType}> {
|
|
1849
|
+
return this.post<${method.returnType}>(\`\${this.resource}/list\`, { ...params, include: ${JSON.stringify(method.includeSpec)} });
|
|
1850
|
+
}
|
|
1851
|
+
`;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1650
1854
|
return `/* Generated. Do not edit. */
|
|
1651
1855
|
import { BaseClient } from "./base-client${ext}";
|
|
1652
|
-
|
|
1653
|
-
|
|
1856
|
+
${typeImports}
|
|
1857
|
+
${otherTableImports.join(`
|
|
1858
|
+
`)}
|
|
1654
1859
|
|
|
1655
1860
|
/**
|
|
1656
1861
|
* Client for ${table.name} table operations
|
|
@@ -1668,7 +1873,6 @@ export class ${Type}Client extends BaseClient {
|
|
|
1668
1873
|
}
|
|
1669
1874
|
|
|
1670
1875
|
async list(params?: {
|
|
1671
|
-
include?: ${Type}IncludeSpec;
|
|
1672
1876
|
limit?: number;
|
|
1673
1877
|
offset?: number;
|
|
1674
1878
|
where?: any;
|
|
@@ -1687,7 +1891,7 @@ export class ${Type}Client extends BaseClient {
|
|
|
1687
1891
|
const path = ${pkPathExpr};
|
|
1688
1892
|
return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
1689
1893
|
}
|
|
1690
|
-
}
|
|
1894
|
+
${includeMethodsCode}}
|
|
1691
1895
|
`;
|
|
1692
1896
|
}
|
|
1693
1897
|
function emitClientIndex(tables, useJsExtensions) {
|
|
@@ -1731,8 +1935,6 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
|
|
|
1731
1935
|
|
|
1732
1936
|
`;
|
|
1733
1937
|
out += `export { BaseClient } from "./base-client${ext}";
|
|
1734
|
-
`;
|
|
1735
|
-
out += `export * from "./include-spec${ext}";
|
|
1736
1938
|
`;
|
|
1737
1939
|
out += `
|
|
1738
1940
|
// Zod schemas for form validation
|
|
@@ -2809,12 +3011,12 @@ function emitTableTest(table, model, clientPath, framework = "vitest") {
|
|
|
2809
3011
|
const Type = pascal(table.name);
|
|
2810
3012
|
const tableName = table.name;
|
|
2811
3013
|
const imports = getFrameworkImports(framework);
|
|
2812
|
-
const
|
|
3014
|
+
const isJunctionTable2 = table.pk.length > 1;
|
|
2813
3015
|
const hasForeignKeys = table.fks.length > 0;
|
|
2814
3016
|
const foreignKeySetup = hasForeignKeys ? generateForeignKeySetup(table, model, clientPath) : null;
|
|
2815
3017
|
const sampleData = generateSampleDataFromSchema(table, hasForeignKeys);
|
|
2816
3018
|
const updateData = generateUpdateDataFromSchema(table);
|
|
2817
|
-
if (
|
|
3019
|
+
if (isJunctionTable2) {
|
|
2818
3020
|
return `${imports}
|
|
2819
3021
|
import { SDK } from '${clientPath}';
|
|
2820
3022
|
import type { Insert${Type}, Update${Type}, Select${Type} } from '${clientPath}/types/${tableName}';
|
|
@@ -3661,7 +3863,11 @@ async function generate(configPath) {
|
|
|
3661
3863
|
});
|
|
3662
3864
|
files.push({
|
|
3663
3865
|
path: join(clientDir, `${table.name}.ts`),
|
|
3664
|
-
content: emitClient(table,
|
|
3866
|
+
content: emitClient(table, graph, {
|
|
3867
|
+
useJsExtensions: cfg.useJsExtensionsClient,
|
|
3868
|
+
includeMethodsDepth: cfg.includeMethodsDepth ?? 2,
|
|
3869
|
+
skipJunctionTables: cfg.skipJunctionTables ?? true
|
|
3870
|
+
})
|
|
3665
3871
|
});
|
|
3666
3872
|
}
|
|
3667
3873
|
files.push({
|
package/dist/types.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface Config {
|
|
|
25
25
|
outClient?: string;
|
|
26
26
|
softDeleteColumn?: string | null;
|
|
27
27
|
includeDepthLimit?: number;
|
|
28
|
+
includeMethodsDepth?: number;
|
|
29
|
+
skipJunctionTables?: boolean;
|
|
28
30
|
serverFramework?: "hono" | "express" | "fastify";
|
|
29
31
|
auth?: AuthConfigInput;
|
|
30
32
|
pull?: PullConfig;
|