postgresdk 0.1.2-alpha.3 → 0.2.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/README.md +143 -42
- package/dist/cli-init.d.ts +2 -0
- package/dist/cli-pull.d.ts +1 -0
- package/dist/cli.js +525 -119
- package/dist/emit-base-client.d.ts +5 -0
- package/dist/emit-sdk-bundle.d.ts +8 -0
- package/dist/index.js +236 -100
- package/dist/types.d.ts +6 -0
- package/package.json +5 -2
package/dist/index.js
CHANGED
@@ -16,6 +16,16 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
16
16
|
return to;
|
17
17
|
};
|
18
18
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
19
|
+
var __export = (target, all) => {
|
20
|
+
for (var name in all)
|
21
|
+
__defProp(target, name, {
|
22
|
+
get: all[name],
|
23
|
+
enumerable: true,
|
24
|
+
configurable: true,
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
26
|
+
});
|
27
|
+
};
|
28
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
19
29
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
20
30
|
|
21
31
|
// node_modules/dotenv/package.json
|
@@ -957,13 +967,116 @@ function emitClient(table) {
|
|
957
967
|
const pkType = hasCompositePk ? `{ ${safePk.map((c) => `${c}: string`).join("; ")} }` : `string`;
|
958
968
|
const pkPathExpr = hasCompositePk ? safePk.map((c) => `pk.${c}`).join(` + "/" + `) : `pk`;
|
959
969
|
return `/* Generated. Do not edit. */
|
970
|
+
import { BaseClient } from "./base-client";
|
960
971
|
import type { ${Type}IncludeSpec } from "./include-spec";
|
961
972
|
import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}";
|
962
973
|
|
963
|
-
|
964
|
-
|
974
|
+
/**
|
975
|
+
* Client for ${table.name} table operations
|
976
|
+
*/
|
977
|
+
export class ${Type}Client extends BaseClient {
|
978
|
+
private readonly resource = "/v1/${table.name}";
|
979
|
+
|
980
|
+
async create(data: Insert${Type}): Promise<Select${Type}> {
|
981
|
+
return this.post<Select${Type}>(this.resource, data);
|
982
|
+
}
|
983
|
+
|
984
|
+
async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
|
985
|
+
const path = ${pkPathExpr};
|
986
|
+
return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
987
|
+
}
|
988
|
+
|
989
|
+
async list(params?: {
|
990
|
+
include?: ${Type}IncludeSpec;
|
991
|
+
limit?: number;
|
992
|
+
offset?: number;
|
993
|
+
where?: any;
|
994
|
+
orderBy?: string;
|
995
|
+
order?: "asc" | "desc";
|
996
|
+
}): Promise<Select${Type}[]> {
|
997
|
+
return this.post<Select${Type}[]>(\`\${this.resource}/list\`, params ?? {});
|
998
|
+
}
|
999
|
+
|
1000
|
+
async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
|
1001
|
+
const path = ${pkPathExpr};
|
1002
|
+
return this.patch<Select${Type} | null>(\`\${this.resource}/\${path}\`, patch);
|
1003
|
+
}
|
965
1004
|
|
966
|
-
|
1005
|
+
async delete(pk: ${pkType}): Promise<Select${Type} | null> {
|
1006
|
+
const path = ${pkPathExpr};
|
1007
|
+
return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
1008
|
+
}
|
1009
|
+
}
|
1010
|
+
`;
|
1011
|
+
}
|
1012
|
+
function emitClientIndex(tables) {
|
1013
|
+
let out = `/* Generated. Do not edit. */
|
1014
|
+
`;
|
1015
|
+
out += `import { BaseClient, AuthConfig } from "./base-client";
|
1016
|
+
`;
|
1017
|
+
for (const t of tables) {
|
1018
|
+
out += `import { ${pascal(t.name)}Client } from "./${t.name}";
|
1019
|
+
`;
|
1020
|
+
}
|
1021
|
+
out += `
|
1022
|
+
// Re-export auth types for convenience
|
1023
|
+
`;
|
1024
|
+
out += `export type { AuthConfig as SDKAuth, AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client";
|
1025
|
+
|
1026
|
+
`;
|
1027
|
+
out += `/**
|
1028
|
+
`;
|
1029
|
+
out += ` * Main SDK class that provides access to all table clients
|
1030
|
+
`;
|
1031
|
+
out += ` */
|
1032
|
+
`;
|
1033
|
+
out += `export class SDK {
|
1034
|
+
`;
|
1035
|
+
for (const t of tables) {
|
1036
|
+
out += ` public ${t.name}: ${pascal(t.name)}Client;
|
1037
|
+
`;
|
1038
|
+
}
|
1039
|
+
out += `
|
1040
|
+
constructor(cfg: { baseUrl: string; fetch?: typeof fetch; auth?: AuthConfig }) {
|
1041
|
+
`;
|
1042
|
+
out += ` const f = cfg.fetch ?? fetch;
|
1043
|
+
`;
|
1044
|
+
for (const t of tables) {
|
1045
|
+
out += ` this.${t.name} = new ${pascal(t.name)}Client(cfg.baseUrl, f, cfg.auth);
|
1046
|
+
`;
|
1047
|
+
}
|
1048
|
+
out += ` }
|
1049
|
+
`;
|
1050
|
+
out += `}
|
1051
|
+
|
1052
|
+
`;
|
1053
|
+
out += `// Export individual table clients
|
1054
|
+
`;
|
1055
|
+
for (const t of tables) {
|
1056
|
+
out += `export { ${pascal(t.name)}Client } from "./${t.name}";
|
1057
|
+
`;
|
1058
|
+
}
|
1059
|
+
out += `
|
1060
|
+
// Export base client for custom extensions
|
1061
|
+
`;
|
1062
|
+
out += `export { BaseClient } from "./base-client";
|
1063
|
+
`;
|
1064
|
+
out += `
|
1065
|
+
// Export include specifications
|
1066
|
+
`;
|
1067
|
+
out += `export * from "./include-spec";
|
1068
|
+
`;
|
1069
|
+
return out;
|
1070
|
+
}
|
1071
|
+
|
1072
|
+
// src/emit-base-client.ts
|
1073
|
+
function emitBaseClient() {
|
1074
|
+
return `/* Generated. Do not edit. */
|
1075
|
+
|
1076
|
+
export type HeaderMap = Record<string, string>;
|
1077
|
+
export type AuthHeadersProvider = () => Promise<HeaderMap> | HeaderMap;
|
1078
|
+
|
1079
|
+
export type AuthConfig =
|
967
1080
|
| AuthHeadersProvider
|
968
1081
|
| {
|
969
1082
|
apiKey?: string;
|
@@ -975,14 +1088,18 @@ type AuthConfig =
|
|
975
1088
|
headers?: AuthHeadersProvider;
|
976
1089
|
};
|
977
1090
|
|
978
|
-
|
1091
|
+
/**
|
1092
|
+
* Base client class with shared authentication and request handling logic.
|
1093
|
+
* All table-specific clients extend this class.
|
1094
|
+
*/
|
1095
|
+
export abstract class BaseClient {
|
979
1096
|
constructor(
|
980
|
-
|
981
|
-
|
982
|
-
|
1097
|
+
protected baseUrl: string,
|
1098
|
+
protected fetchFn: typeof fetch = fetch,
|
1099
|
+
protected auth?: AuthConfig
|
983
1100
|
) {}
|
984
1101
|
|
985
|
-
|
1102
|
+
protected async authHeaders(): Promise<HeaderMap> {
|
986
1103
|
if (!this.auth) return {};
|
987
1104
|
if (typeof this.auth === "function") {
|
988
1105
|
const h = await this.auth();
|
@@ -1008,129 +1125,91 @@ export class ${Type}Client {
|
|
1008
1125
|
return out;
|
1009
1126
|
}
|
1010
1127
|
|
1011
|
-
|
1128
|
+
protected async headers(json = false): Promise<HeaderMap> {
|
1012
1129
|
const extra = await this.authHeaders();
|
1013
1130
|
return json ? { "Content-Type": "application/json", ...extra } : extra;
|
1014
1131
|
}
|
1015
1132
|
|
1016
|
-
|
1133
|
+
protected async okOrThrow(res: Response, action: string, entity: string): Promise<void> {
|
1017
1134
|
if (!res.ok) {
|
1018
1135
|
let detail = "";
|
1019
1136
|
try { detail = await res.text(); } catch {}
|
1020
|
-
throw new Error(\`\${action}
|
1137
|
+
throw new Error(\`\${action} \${entity} failed: \${res.status} \${detail}\`);
|
1021
1138
|
}
|
1022
1139
|
}
|
1023
1140
|
|
1024
|
-
|
1025
|
-
|
1141
|
+
/**
|
1142
|
+
* Make a POST request
|
1143
|
+
*/
|
1144
|
+
protected async post<T>(path: string, body?: any): Promise<T> {
|
1145
|
+
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
1026
1146
|
method: "POST",
|
1027
1147
|
headers: await this.headers(true),
|
1028
|
-
body: JSON.stringify(
|
1148
|
+
body: JSON.stringify(body),
|
1029
1149
|
});
|
1030
|
-
|
1031
|
-
|
1150
|
+
|
1151
|
+
// Handle 404 specially for operations that might return null
|
1152
|
+
if (res.status === 404) {
|
1153
|
+
return null as T;
|
1154
|
+
}
|
1155
|
+
|
1156
|
+
await this.okOrThrow(res, "POST", path);
|
1157
|
+
return (await res.json()) as T;
|
1032
1158
|
}
|
1033
1159
|
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1160
|
+
/**
|
1161
|
+
* Make a GET request
|
1162
|
+
*/
|
1163
|
+
protected async get<T>(path: string): Promise<T> {
|
1164
|
+
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
1037
1165
|
headers: await this.headers(),
|
1038
1166
|
});
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
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}[];
|
1167
|
+
|
1168
|
+
if (res.status === 404) {
|
1169
|
+
return null as T;
|
1170
|
+
}
|
1171
|
+
|
1172
|
+
await this.okOrThrow(res, "GET", path);
|
1173
|
+
return (await res.json()) as T;
|
1052
1174
|
}
|
1053
1175
|
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1176
|
+
/**
|
1177
|
+
* Make a PATCH request
|
1178
|
+
*/
|
1179
|
+
protected async patch<T>(path: string, body?: any): Promise<T> {
|
1180
|
+
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
1057
1181
|
method: "PATCH",
|
1058
1182
|
headers: await this.headers(true),
|
1059
|
-
body: JSON.stringify(
|
1183
|
+
body: JSON.stringify(body),
|
1060
1184
|
});
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1185
|
+
|
1186
|
+
if (res.status === 404) {
|
1187
|
+
return null as T;
|
1188
|
+
}
|
1189
|
+
|
1190
|
+
await this.okOrThrow(res, "PATCH", path);
|
1191
|
+
return (await res.json()) as T;
|
1064
1192
|
}
|
1065
1193
|
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1194
|
+
/**
|
1195
|
+
* Make a DELETE request
|
1196
|
+
*/
|
1197
|
+
protected async del<T>(path: string): Promise<T> {
|
1198
|
+
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
1069
1199
|
method: "DELETE",
|
1070
1200
|
headers: await this.headers(),
|
1071
1201
|
});
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1202
|
+
|
1203
|
+
if (res.status === 404) {
|
1204
|
+
return null as T;
|
1205
|
+
}
|
1206
|
+
|
1207
|
+
await this.okOrThrow(res, "DELETE", path);
|
1208
|
+
return (await res.json()) as T;
|
1075
1209
|
}
|
1076
1210
|
}
|
1077
1211
|
`;
|
1078
1212
|
}
|
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
1213
|
|
1135
1214
|
// src/emit-include-loader.ts
|
1136
1215
|
function emitIncludeLoader(graph, model, maxDepth) {
|
@@ -1678,6 +1757,7 @@ function emitRouter(tables, hasAuth) {
|
|
1678
1757
|
`);
|
1679
1758
|
return `/* Generated. Do not edit. */
|
1680
1759
|
import { Hono } from "hono";
|
1760
|
+
import { SDK_MANIFEST } from "./sdk-bundle";
|
1681
1761
|
${imports}
|
1682
1762
|
${hasAuth ? `export { authMiddleware } from "./auth";` : ""}
|
1683
1763
|
|
@@ -1705,7 +1785,34 @@ export function createRouter(
|
|
1705
1785
|
deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }
|
1706
1786
|
): Hono {
|
1707
1787
|
const router = new Hono();
|
1788
|
+
|
1789
|
+
// Register table routes
|
1708
1790
|
${registrations}
|
1791
|
+
|
1792
|
+
// SDK distribution endpoints
|
1793
|
+
router.get("/sdk/manifest", (c) => {
|
1794
|
+
return c.json({
|
1795
|
+
version: SDK_MANIFEST.version,
|
1796
|
+
generated: SDK_MANIFEST.generated,
|
1797
|
+
files: Object.keys(SDK_MANIFEST.files)
|
1798
|
+
});
|
1799
|
+
});
|
1800
|
+
|
1801
|
+
router.get("/sdk/download", (c) => {
|
1802
|
+
return c.json(SDK_MANIFEST);
|
1803
|
+
});
|
1804
|
+
|
1805
|
+
router.get("/sdk/files/:path{.*}", (c) => {
|
1806
|
+
const path = c.req.param("path");
|
1807
|
+
const content = SDK_MANIFEST.files[path];
|
1808
|
+
if (!content) {
|
1809
|
+
return c.text("File not found", 404);
|
1810
|
+
}
|
1811
|
+
return c.text(content, 200, {
|
1812
|
+
"Content-Type": "text/plain; charset=utf-8"
|
1813
|
+
});
|
1814
|
+
});
|
1815
|
+
|
1709
1816
|
return router;
|
1710
1817
|
}
|
1711
1818
|
|
@@ -1739,6 +1846,29 @@ export * from "./include-spec";
|
|
1739
1846
|
`;
|
1740
1847
|
}
|
1741
1848
|
|
1849
|
+
// src/emit-sdk-bundle.ts
|
1850
|
+
function emitSdkBundle(clientFiles) {
|
1851
|
+
const files = {};
|
1852
|
+
for (const file of clientFiles) {
|
1853
|
+
const parts = file.path.split("/");
|
1854
|
+
const clientIndex = parts.lastIndexOf("client");
|
1855
|
+
if (clientIndex >= 0 && clientIndex < parts.length - 1) {
|
1856
|
+
const relativePath = parts.slice(clientIndex + 1).join("/");
|
1857
|
+
files[relativePath] = file.content;
|
1858
|
+
}
|
1859
|
+
}
|
1860
|
+
const version = `1.0.0`;
|
1861
|
+
const generated = new Date().toISOString();
|
1862
|
+
return `/* Generated. Do not edit. */
|
1863
|
+
|
1864
|
+
export const SDK_MANIFEST = {
|
1865
|
+
version: "${version}",
|
1866
|
+
generated: "${generated}",
|
1867
|
+
files: ${JSON.stringify(files, null, 2)}
|
1868
|
+
};
|
1869
|
+
`;
|
1870
|
+
}
|
1871
|
+
|
1742
1872
|
// src/types.ts
|
1743
1873
|
function normalizeAuthConfig(input) {
|
1744
1874
|
if (!input)
|
@@ -1803,6 +1933,7 @@ async function generate(configPath) {
|
|
1803
1933
|
const includeSpec = emitIncludeSpec(graph);
|
1804
1934
|
files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
|
1805
1935
|
files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
|
1936
|
+
files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
|
1806
1937
|
files.push({
|
1807
1938
|
path: join(serverDir, "include-builder.ts"),
|
1808
1939
|
content: emitIncludeBuilder(graph, cfg.includeDepthLimit || 3)
|
@@ -1844,6 +1975,11 @@ async function generate(configPath) {
|
|
1844
1975
|
path: join(serverDir, "router.ts"),
|
1845
1976
|
content: emitRouter(Object.values(model.tables), !!normalizedAuth?.strategy && normalizedAuth.strategy !== "none")
|
1846
1977
|
});
|
1978
|
+
const clientFiles = files.filter((f) => f.path.includes(clientDir));
|
1979
|
+
files.push({
|
1980
|
+
path: join(serverDir, "sdk-bundle.ts"),
|
1981
|
+
content: emitSdkBundle(clientFiles)
|
1982
|
+
});
|
1847
1983
|
console.log("✍️ Writing files...");
|
1848
1984
|
await writeFiles(files);
|
1849
1985
|
console.log(`✅ Generated ${files.length} files`);
|
package/dist/types.d.ts
CHANGED
@@ -27,5 +27,11 @@ export interface Config {
|
|
27
27
|
includeDepthLimit?: number;
|
28
28
|
dateType?: "date" | "string";
|
29
29
|
auth?: AuthConfigInput;
|
30
|
+
pull?: PullConfig;
|
31
|
+
}
|
32
|
+
export interface PullConfig {
|
33
|
+
from: string;
|
34
|
+
output?: string;
|
35
|
+
token?: string;
|
30
36
|
}
|
31
37
|
export declare function normalizeAuthConfig(input: AuthConfigInput | undefined): AuthConfig | undefined;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "postgresdk",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.2.0",
|
4
4
|
"description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
|
5
5
|
"type": "module",
|
6
6
|
"bin": {
|
@@ -22,7 +22,10 @@
|
|
22
22
|
},
|
23
23
|
"scripts": {
|
24
24
|
"build": "bun build src/cli.ts src/index.ts --outdir dist --target node --format esm --external=pg --external=zod --external=hono --external=node:* && tsc -p tsconfig.build.json --emitDeclarationOnly",
|
25
|
-
"test": "bun test
|
25
|
+
"test": "bun test:init && bun test:gen && bun test:pull",
|
26
|
+
"test:init": "bun test/test-init.ts",
|
27
|
+
"test:gen": "bun test/test-gen.ts",
|
28
|
+
"test:pull": "bun test/test-pull.ts",
|
26
29
|
"prepublishOnly": "npm run build",
|
27
30
|
"publish:patch": "./publish.sh",
|
28
31
|
"publish:minor": "./publish.sh",
|