postgresdk 0.1.2-alpha.3 → 0.1.2-alpha.4

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
@@ -958,13 +958,116 @@ function emitClient(table) {
958
958
  const pkType = hasCompositePk ? `{ ${safePk.map((c) => `${c}: string`).join("; ")} }` : `string`;
959
959
  const pkPathExpr = hasCompositePk ? safePk.map((c) => `pk.${c}`).join(` + "/" + `) : `pk`;
960
960
  return `/* Generated. Do not edit. */
961
+ import { BaseClient } from "./base-client";
961
962
  import type { ${Type}IncludeSpec } from "./include-spec";
962
963
  import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}";
963
964
 
964
- type HeaderMap = Record<string, string>;
965
- type AuthHeadersProvider = () => Promise<HeaderMap> | HeaderMap;
965
+ /**
966
+ * Client for ${table.name} table operations
967
+ */
968
+ export class ${Type}Client extends BaseClient {
969
+ private readonly resource = "/v1/${table.name}";
970
+
971
+ async create(data: Insert${Type}): Promise<Select${Type}> {
972
+ return this.post<Select${Type}>(this.resource, data);
973
+ }
974
+
975
+ async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
976
+ const path = ${pkPathExpr};
977
+ return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
978
+ }
979
+
980
+ async list(params?: {
981
+ include?: ${Type}IncludeSpec;
982
+ limit?: number;
983
+ offset?: number;
984
+ where?: any;
985
+ orderBy?: string;
986
+ order?: "asc" | "desc";
987
+ }): Promise<Select${Type}[]> {
988
+ return this.post<Select${Type}[]>(\`\${this.resource}/list\`, params ?? {});
989
+ }
990
+
991
+ async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
992
+ const path = ${pkPathExpr};
993
+ return this.patch<Select${Type} | null>(\`\${this.resource}/\${path}\`, patch);
994
+ }
995
+
996
+ async delete(pk: ${pkType}): Promise<Select${Type} | null> {
997
+ const path = ${pkPathExpr};
998
+ return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
999
+ }
1000
+ }
1001
+ `;
1002
+ }
1003
+ function emitClientIndex(tables) {
1004
+ let out = `/* Generated. Do not edit. */
1005
+ `;
1006
+ out += `import { BaseClient, AuthConfig } from "./base-client";
1007
+ `;
1008
+ for (const t of tables) {
1009
+ out += `import { ${pascal(t.name)}Client } from "./${t.name}";
1010
+ `;
1011
+ }
1012
+ out += `
1013
+ // Re-export auth types for convenience
1014
+ `;
1015
+ out += `export type { AuthConfig as SDKAuth, AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client";
1016
+
1017
+ `;
1018
+ out += `/**
1019
+ `;
1020
+ out += ` * Main SDK class that provides access to all table clients
1021
+ `;
1022
+ out += ` */
1023
+ `;
1024
+ out += `export class SDK {
1025
+ `;
1026
+ for (const t of tables) {
1027
+ out += ` public ${t.name}: ${pascal(t.name)}Client;
1028
+ `;
1029
+ }
1030
+ out += `
1031
+ constructor(cfg: { baseUrl: string; fetch?: typeof fetch; auth?: AuthConfig }) {
1032
+ `;
1033
+ out += ` const f = cfg.fetch ?? fetch;
1034
+ `;
1035
+ for (const t of tables) {
1036
+ out += ` this.${t.name} = new ${pascal(t.name)}Client(cfg.baseUrl, f, cfg.auth);
1037
+ `;
1038
+ }
1039
+ out += ` }
1040
+ `;
1041
+ out += `}
966
1042
 
967
- type AuthConfig =
1043
+ `;
1044
+ out += `// Export individual table clients
1045
+ `;
1046
+ for (const t of tables) {
1047
+ out += `export { ${pascal(t.name)}Client } from "./${t.name}";
1048
+ `;
1049
+ }
1050
+ out += `
1051
+ // Export base client for custom extensions
1052
+ `;
1053
+ out += `export { BaseClient } from "./base-client";
1054
+ `;
1055
+ out += `
1056
+ // Export include specifications
1057
+ `;
1058
+ out += `export * from "./include-spec";
1059
+ `;
1060
+ return out;
1061
+ }
1062
+
1063
+ // src/emit-base-client.ts
1064
+ function emitBaseClient() {
1065
+ return `/* Generated. Do not edit. */
1066
+
1067
+ export type HeaderMap = Record<string, string>;
1068
+ export type AuthHeadersProvider = () => Promise<HeaderMap> | HeaderMap;
1069
+
1070
+ export type AuthConfig =
968
1071
  | AuthHeadersProvider
969
1072
  | {
970
1073
  apiKey?: string;
@@ -976,14 +1079,18 @@ type AuthConfig =
976
1079
  headers?: AuthHeadersProvider;
977
1080
  };
978
1081
 
979
- export class ${Type}Client {
1082
+ /**
1083
+ * Base client class with shared authentication and request handling logic.
1084
+ * All table-specific clients extend this class.
1085
+ */
1086
+ export abstract class BaseClient {
980
1087
  constructor(
981
- private baseUrl: string,
982
- private fetchFn: typeof fetch = fetch,
983
- private auth?: AuthConfig
1088
+ protected baseUrl: string,
1089
+ protected fetchFn: typeof fetch = fetch,
1090
+ protected auth?: AuthConfig
984
1091
  ) {}
985
1092
 
986
- private async authHeaders(): Promise<HeaderMap> {
1093
+ protected async authHeaders(): Promise<HeaderMap> {
987
1094
  if (!this.auth) return {};
988
1095
  if (typeof this.auth === "function") {
989
1096
  const h = await this.auth();
@@ -1009,129 +1116,91 @@ export class ${Type}Client {
1009
1116
  return out;
1010
1117
  }
1011
1118
 
1012
- private async headers(json = false) {
1119
+ protected async headers(json = false): Promise<HeaderMap> {
1013
1120
  const extra = await this.authHeaders();
1014
1121
  return json ? { "Content-Type": "application/json", ...extra } : extra;
1015
1122
  }
1016
1123
 
1017
- private async okOrThrow(res: Response, action: string) {
1124
+ protected async okOrThrow(res: Response, action: string, entity: string): Promise<void> {
1018
1125
  if (!res.ok) {
1019
1126
  let detail = "";
1020
1127
  try { detail = await res.text(); } catch {}
1021
- throw new Error(\`\${action} ${table.name} failed: \${res.status} \${detail}\`);
1128
+ throw new Error(\`\${action} \${entity} failed: \${res.status} \${detail}\`);
1022
1129
  }
1023
1130
  }
1024
1131
 
1025
- async create(data: Insert${Type}): Promise<Select${Type}> {
1026
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}\`, {
1132
+ /**
1133
+ * Make a POST request
1134
+ */
1135
+ protected async post<T>(path: string, body?: any): Promise<T> {
1136
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1027
1137
  method: "POST",
1028
1138
  headers: await this.headers(true),
1029
- body: JSON.stringify(data),
1139
+ body: JSON.stringify(body),
1030
1140
  });
1031
- await this.okOrThrow(res, "create");
1032
- return (await res.json()) as Select${Type};
1141
+
1142
+ // Handle 404 specially for operations that might return null
1143
+ if (res.status === 404) {
1144
+ return null as T;
1145
+ }
1146
+
1147
+ await this.okOrThrow(res, "POST", path);
1148
+ return (await res.json()) as T;
1033
1149
  }
1034
1150
 
1035
- async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
1036
- const path = ${pkPathExpr};
1037
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}/\${path}\`, {
1151
+ /**
1152
+ * Make a GET request
1153
+ */
1154
+ protected async get<T>(path: string): Promise<T> {
1155
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1038
1156
  headers: await this.headers(),
1039
1157
  });
1040
- if (res.status === 404) return null;
1041
- await this.okOrThrow(res, "get");
1042
- return (await res.json()) as Select${Type};
1043
- }
1044
-
1045
- async list(params?: { include?: ${Type}IncludeSpec; limit?: number; offset?: number }): Promise<Select${Type}[]> {
1046
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}/list\`, {
1047
- method: "POST",
1048
- headers: await this.headers(true),
1049
- body: JSON.stringify(params ?? {}),
1050
- });
1051
- await this.okOrThrow(res, "list");
1052
- return (await res.json()) as Select${Type}[];
1158
+
1159
+ if (res.status === 404) {
1160
+ return null as T;
1161
+ }
1162
+
1163
+ await this.okOrThrow(res, "GET", path);
1164
+ return (await res.json()) as T;
1053
1165
  }
1054
1166
 
1055
- async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
1056
- const path = ${pkPathExpr};
1057
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}/\${path}\`, {
1167
+ /**
1168
+ * Make a PATCH request
1169
+ */
1170
+ protected async patch<T>(path: string, body?: any): Promise<T> {
1171
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1058
1172
  method: "PATCH",
1059
1173
  headers: await this.headers(true),
1060
- body: JSON.stringify(patch),
1174
+ body: JSON.stringify(body),
1061
1175
  });
1062
- if (res.status === 404) return null;
1063
- await this.okOrThrow(res, "update");
1064
- return (await res.json()) as Select${Type};
1176
+
1177
+ if (res.status === 404) {
1178
+ return null as T;
1179
+ }
1180
+
1181
+ await this.okOrThrow(res, "PATCH", path);
1182
+ return (await res.json()) as T;
1065
1183
  }
1066
1184
 
1067
- async delete(pk: ${pkType}): Promise<Select${Type} | null> {
1068
- const path = ${pkPathExpr};
1069
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}/\${path}\`, {
1185
+ /**
1186
+ * Make a DELETE request
1187
+ */
1188
+ protected async del<T>(path: string): Promise<T> {
1189
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1070
1190
  method: "DELETE",
1071
1191
  headers: await this.headers(),
1072
1192
  });
1073
- if (res.status === 404) return null;
1074
- await this.okOrThrow(res, "delete");
1075
- return (await res.json()) as Select${Type};
1193
+
1194
+ if (res.status === 404) {
1195
+ return null as T;
1196
+ }
1197
+
1198
+ await this.okOrThrow(res, "DELETE", path);
1199
+ return (await res.json()) as T;
1076
1200
  }
1077
1201
  }
1078
1202
  `;
1079
1203
  }
1080
- function emitClientIndex(tables) {
1081
- let out = `/* Generated. Do not edit. */
1082
- `;
1083
- for (const t of tables) {
1084
- out += `import { ${pascal(t.name)}Client } from "./${t.name}";
1085
- `;
1086
- }
1087
- out += `
1088
- export type SDKAuthHeadersProvider = () => Promise<Record<string,string>> | Record<string,string>;
1089
- `;
1090
- out += `export type SDKAuth =
1091
- `;
1092
- out += ` | SDKAuthHeadersProvider
1093
- `;
1094
- out += ` | {
1095
- `;
1096
- out += ` apiKey?: string;
1097
- `;
1098
- out += ` /** defaults to "x-api-key" */
1099
- `;
1100
- out += ` apiKeyHeader?: string;
1101
- `;
1102
- out += ` jwt?: string | (() => Promise<string>);
1103
- `;
1104
- out += ` headers?: SDKAuthHeadersProvider;
1105
- `;
1106
- out += ` };
1107
-
1108
- `;
1109
- out += `export class SDK {
1110
- `;
1111
- for (const t of tables) {
1112
- out += ` public ${t.name}: ${pascal(t.name)}Client;
1113
- `;
1114
- }
1115
- out += `
1116
- constructor(cfg: { baseUrl: string; fetch?: typeof fetch; auth?: SDKAuth }) {
1117
- `;
1118
- out += ` const f = cfg.fetch ?? fetch;
1119
- `;
1120
- for (const t of tables) {
1121
- out += ` this.${t.name} = new ${pascal(t.name)}Client(cfg.baseUrl, f, cfg.auth);
1122
- `;
1123
- }
1124
- out += ` }
1125
- `;
1126
- out += `}
1127
- `;
1128
- for (const t of tables)
1129
- out += `export { ${pascal(t.name)}Client } from "./${t.name}";
1130
- `;
1131
- out += `export * from "./include-spec";
1132
- `;
1133
- return out;
1134
- }
1135
1204
 
1136
1205
  // src/emit-include-loader.ts
1137
1206
  function emitIncludeLoader(graph, model, maxDepth) {
@@ -1804,6 +1873,7 @@ async function generate(configPath) {
1804
1873
  const includeSpec = emitIncludeSpec(graph);
1805
1874
  files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
1806
1875
  files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
1876
+ files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
1807
1877
  files.push({
1808
1878
  path: join(serverDir, "include-builder.ts"),
1809
1879
  content: emitIncludeBuilder(graph, cfg.includeDepthLimit || 3)
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Emits the BaseClient class that all table-specific clients will extend.
3
+ * Contains all shared logic for auth, headers, and HTTP operations.
4
+ */
5
+ export declare function emitBaseClient(): string;
package/dist/index.js CHANGED
@@ -957,13 +957,116 @@ function emitClient(table) {
957
957
  const pkType = hasCompositePk ? `{ ${safePk.map((c) => `${c}: string`).join("; ")} }` : `string`;
958
958
  const pkPathExpr = hasCompositePk ? safePk.map((c) => `pk.${c}`).join(` + "/" + `) : `pk`;
959
959
  return `/* Generated. Do not edit. */
960
+ import { BaseClient } from "./base-client";
960
961
  import type { ${Type}IncludeSpec } from "./include-spec";
961
962
  import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}";
962
963
 
963
- type HeaderMap = Record<string, string>;
964
- type AuthHeadersProvider = () => Promise<HeaderMap> | HeaderMap;
964
+ /**
965
+ * Client for ${table.name} table operations
966
+ */
967
+ export class ${Type}Client extends BaseClient {
968
+ private readonly resource = "/v1/${table.name}";
969
+
970
+ async create(data: Insert${Type}): Promise<Select${Type}> {
971
+ return this.post<Select${Type}>(this.resource, data);
972
+ }
973
+
974
+ async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
975
+ const path = ${pkPathExpr};
976
+ return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
977
+ }
978
+
979
+ async list(params?: {
980
+ include?: ${Type}IncludeSpec;
981
+ limit?: number;
982
+ offset?: number;
983
+ where?: any;
984
+ orderBy?: string;
985
+ order?: "asc" | "desc";
986
+ }): Promise<Select${Type}[]> {
987
+ return this.post<Select${Type}[]>(\`\${this.resource}/list\`, params ?? {});
988
+ }
989
+
990
+ async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
991
+ const path = ${pkPathExpr};
992
+ return this.patch<Select${Type} | null>(\`\${this.resource}/\${path}\`, patch);
993
+ }
994
+
995
+ async delete(pk: ${pkType}): Promise<Select${Type} | null> {
996
+ const path = ${pkPathExpr};
997
+ return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
998
+ }
999
+ }
1000
+ `;
1001
+ }
1002
+ function emitClientIndex(tables) {
1003
+ let out = `/* Generated. Do not edit. */
1004
+ `;
1005
+ out += `import { BaseClient, AuthConfig } from "./base-client";
1006
+ `;
1007
+ for (const t of tables) {
1008
+ out += `import { ${pascal(t.name)}Client } from "./${t.name}";
1009
+ `;
1010
+ }
1011
+ out += `
1012
+ // Re-export auth types for convenience
1013
+ `;
1014
+ out += `export type { AuthConfig as SDKAuth, AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client";
1015
+
1016
+ `;
1017
+ out += `/**
1018
+ `;
1019
+ out += ` * Main SDK class that provides access to all table clients
1020
+ `;
1021
+ out += ` */
1022
+ `;
1023
+ out += `export class SDK {
1024
+ `;
1025
+ for (const t of tables) {
1026
+ out += ` public ${t.name}: ${pascal(t.name)}Client;
1027
+ `;
1028
+ }
1029
+ out += `
1030
+ constructor(cfg: { baseUrl: string; fetch?: typeof fetch; auth?: AuthConfig }) {
1031
+ `;
1032
+ out += ` const f = cfg.fetch ?? fetch;
1033
+ `;
1034
+ for (const t of tables) {
1035
+ out += ` this.${t.name} = new ${pascal(t.name)}Client(cfg.baseUrl, f, cfg.auth);
1036
+ `;
1037
+ }
1038
+ out += ` }
1039
+ `;
1040
+ out += `}
965
1041
 
966
- type AuthConfig =
1042
+ `;
1043
+ out += `// Export individual table clients
1044
+ `;
1045
+ for (const t of tables) {
1046
+ out += `export { ${pascal(t.name)}Client } from "./${t.name}";
1047
+ `;
1048
+ }
1049
+ out += `
1050
+ // Export base client for custom extensions
1051
+ `;
1052
+ out += `export { BaseClient } from "./base-client";
1053
+ `;
1054
+ out += `
1055
+ // Export include specifications
1056
+ `;
1057
+ out += `export * from "./include-spec";
1058
+ `;
1059
+ return out;
1060
+ }
1061
+
1062
+ // src/emit-base-client.ts
1063
+ function emitBaseClient() {
1064
+ return `/* Generated. Do not edit. */
1065
+
1066
+ export type HeaderMap = Record<string, string>;
1067
+ export type AuthHeadersProvider = () => Promise<HeaderMap> | HeaderMap;
1068
+
1069
+ export type AuthConfig =
967
1070
  | AuthHeadersProvider
968
1071
  | {
969
1072
  apiKey?: string;
@@ -975,14 +1078,18 @@ type AuthConfig =
975
1078
  headers?: AuthHeadersProvider;
976
1079
  };
977
1080
 
978
- export class ${Type}Client {
1081
+ /**
1082
+ * Base client class with shared authentication and request handling logic.
1083
+ * All table-specific clients extend this class.
1084
+ */
1085
+ export abstract class BaseClient {
979
1086
  constructor(
980
- private baseUrl: string,
981
- private fetchFn: typeof fetch = fetch,
982
- private auth?: AuthConfig
1087
+ protected baseUrl: string,
1088
+ protected fetchFn: typeof fetch = fetch,
1089
+ protected auth?: AuthConfig
983
1090
  ) {}
984
1091
 
985
- private async authHeaders(): Promise<HeaderMap> {
1092
+ protected async authHeaders(): Promise<HeaderMap> {
986
1093
  if (!this.auth) return {};
987
1094
  if (typeof this.auth === "function") {
988
1095
  const h = await this.auth();
@@ -1008,129 +1115,91 @@ export class ${Type}Client {
1008
1115
  return out;
1009
1116
  }
1010
1117
 
1011
- private async headers(json = false) {
1118
+ protected async headers(json = false): Promise<HeaderMap> {
1012
1119
  const extra = await this.authHeaders();
1013
1120
  return json ? { "Content-Type": "application/json", ...extra } : extra;
1014
1121
  }
1015
1122
 
1016
- private async okOrThrow(res: Response, action: string) {
1123
+ protected async okOrThrow(res: Response, action: string, entity: string): Promise<void> {
1017
1124
  if (!res.ok) {
1018
1125
  let detail = "";
1019
1126
  try { detail = await res.text(); } catch {}
1020
- throw new Error(\`\${action} ${table.name} failed: \${res.status} \${detail}\`);
1127
+ throw new Error(\`\${action} \${entity} failed: \${res.status} \${detail}\`);
1021
1128
  }
1022
1129
  }
1023
1130
 
1024
- async create(data: Insert${Type}): Promise<Select${Type}> {
1025
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}\`, {
1131
+ /**
1132
+ * Make a POST request
1133
+ */
1134
+ protected async post<T>(path: string, body?: any): Promise<T> {
1135
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1026
1136
  method: "POST",
1027
1137
  headers: await this.headers(true),
1028
- body: JSON.stringify(data),
1138
+ body: JSON.stringify(body),
1029
1139
  });
1030
- await this.okOrThrow(res, "create");
1031
- return (await res.json()) as Select${Type};
1140
+
1141
+ // Handle 404 specially for operations that might return null
1142
+ if (res.status === 404) {
1143
+ return null as T;
1144
+ }
1145
+
1146
+ await this.okOrThrow(res, "POST", path);
1147
+ return (await res.json()) as T;
1032
1148
  }
1033
1149
 
1034
- async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
1035
- const path = ${pkPathExpr};
1036
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}/\${path}\`, {
1150
+ /**
1151
+ * Make a GET request
1152
+ */
1153
+ protected async get<T>(path: string): Promise<T> {
1154
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1037
1155
  headers: await this.headers(),
1038
1156
  });
1039
- if (res.status === 404) return null;
1040
- await this.okOrThrow(res, "get");
1041
- return (await res.json()) as Select${Type};
1042
- }
1043
-
1044
- async list(params?: { include?: ${Type}IncludeSpec; limit?: number; offset?: number }): Promise<Select${Type}[]> {
1045
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}/list\`, {
1046
- method: "POST",
1047
- headers: await this.headers(true),
1048
- body: JSON.stringify(params ?? {}),
1049
- });
1050
- await this.okOrThrow(res, "list");
1051
- return (await res.json()) as Select${Type}[];
1157
+
1158
+ if (res.status === 404) {
1159
+ return null as T;
1160
+ }
1161
+
1162
+ await this.okOrThrow(res, "GET", path);
1163
+ return (await res.json()) as T;
1052
1164
  }
1053
1165
 
1054
- async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
1055
- const path = ${pkPathExpr};
1056
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}/\${path}\`, {
1166
+ /**
1167
+ * Make a PATCH request
1168
+ */
1169
+ protected async patch<T>(path: string, body?: any): Promise<T> {
1170
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1057
1171
  method: "PATCH",
1058
1172
  headers: await this.headers(true),
1059
- body: JSON.stringify(patch),
1173
+ body: JSON.stringify(body),
1060
1174
  });
1061
- if (res.status === 404) return null;
1062
- await this.okOrThrow(res, "update");
1063
- return (await res.json()) as Select${Type};
1175
+
1176
+ if (res.status === 404) {
1177
+ return null as T;
1178
+ }
1179
+
1180
+ await this.okOrThrow(res, "PATCH", path);
1181
+ return (await res.json()) as T;
1064
1182
  }
1065
1183
 
1066
- async delete(pk: ${pkType}): Promise<Select${Type} | null> {
1067
- const path = ${pkPathExpr};
1068
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}/\${path}\`, {
1184
+ /**
1185
+ * Make a DELETE request
1186
+ */
1187
+ protected async del<T>(path: string): Promise<T> {
1188
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1069
1189
  method: "DELETE",
1070
1190
  headers: await this.headers(),
1071
1191
  });
1072
- if (res.status === 404) return null;
1073
- await this.okOrThrow(res, "delete");
1074
- return (await res.json()) as Select${Type};
1192
+
1193
+ if (res.status === 404) {
1194
+ return null as T;
1195
+ }
1196
+
1197
+ await this.okOrThrow(res, "DELETE", path);
1198
+ return (await res.json()) as T;
1075
1199
  }
1076
1200
  }
1077
1201
  `;
1078
1202
  }
1079
- function emitClientIndex(tables) {
1080
- let out = `/* Generated. Do not edit. */
1081
- `;
1082
- for (const t of tables) {
1083
- out += `import { ${pascal(t.name)}Client } from "./${t.name}";
1084
- `;
1085
- }
1086
- out += `
1087
- export type SDKAuthHeadersProvider = () => Promise<Record<string,string>> | Record<string,string>;
1088
- `;
1089
- out += `export type SDKAuth =
1090
- `;
1091
- out += ` | SDKAuthHeadersProvider
1092
- `;
1093
- out += ` | {
1094
- `;
1095
- out += ` apiKey?: string;
1096
- `;
1097
- out += ` /** defaults to "x-api-key" */
1098
- `;
1099
- out += ` apiKeyHeader?: string;
1100
- `;
1101
- out += ` jwt?: string | (() => Promise<string>);
1102
- `;
1103
- out += ` headers?: SDKAuthHeadersProvider;
1104
- `;
1105
- out += ` };
1106
-
1107
- `;
1108
- out += `export class SDK {
1109
- `;
1110
- for (const t of tables) {
1111
- out += ` public ${t.name}: ${pascal(t.name)}Client;
1112
- `;
1113
- }
1114
- out += `
1115
- constructor(cfg: { baseUrl: string; fetch?: typeof fetch; auth?: SDKAuth }) {
1116
- `;
1117
- out += ` const f = cfg.fetch ?? fetch;
1118
- `;
1119
- for (const t of tables) {
1120
- out += ` this.${t.name} = new ${pascal(t.name)}Client(cfg.baseUrl, f, cfg.auth);
1121
- `;
1122
- }
1123
- out += ` }
1124
- `;
1125
- out += `}
1126
- `;
1127
- for (const t of tables)
1128
- out += `export { ${pascal(t.name)}Client } from "./${t.name}";
1129
- `;
1130
- out += `export * from "./include-spec";
1131
- `;
1132
- return out;
1133
- }
1134
1203
 
1135
1204
  // src/emit-include-loader.ts
1136
1205
  function emitIncludeLoader(graph, model, maxDepth) {
@@ -1803,6 +1872,7 @@ async function generate(configPath) {
1803
1872
  const includeSpec = emitIncludeSpec(graph);
1804
1873
  files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
1805
1874
  files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
1875
+ files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
1806
1876
  files.push({
1807
1877
  path: join(serverDir, "include-builder.ts"),
1808
1878
  content: emitIncludeBuilder(graph, cfg.includeDepthLimit || 3)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.1.2-alpha.3",
3
+ "version": "0.1.2-alpha.4",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {