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 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 fields = table.columns.map((c) => {
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
- ${fields}
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
- function emitClient(table, useJsExtensions) {
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
- import type { ${Type}IncludeSpec } from "./include-spec${ext}";
1916
- import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";
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 isJunctionTable = table.pk.length > 1;
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 (isJunctionTable) {
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, cfg.useJsExtensionsClient)
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({
@@ -1,3 +1,8 @@
1
1
  import type { Table } from "./introspect";
2
- export declare function emitClient(table: Table, useJsExtensions?: boolean): string;
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 fields = table.columns.map((c) => {
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
- ${fields}
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
- function emitClient(table, useJsExtensions) {
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
- import type { ${Type}IncludeSpec } from "./include-spec${ext}";
1653
- import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";
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 isJunctionTable = table.pk.length > 1;
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 (isJunctionTable) {
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, cfg.useJsExtensionsClient)
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.7.4",
3
+ "version": "0.8.0",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {