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/dist/cli.js CHANGED
@@ -17,6 +17,16 @@ var __toESM = (mod, isNodeMode, target) => {
17
17
  return to;
18
18
  };
19
19
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
20
30
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
21
31
 
22
32
  // node_modules/dotenv/package.json
@@ -460,6 +470,242 @@ var require_config = __commonJS(() => {
460
470
  })();
461
471
  });
462
472
 
473
+ // src/cli-init.ts
474
+ var exports_cli_init = {};
475
+ __export(exports_cli_init, {
476
+ initCommand: () => initCommand
477
+ });
478
+ import { existsSync, writeFileSync } from "fs";
479
+ import { resolve } from "path";
480
+ async function initCommand(args) {
481
+ console.log(`\uD83D\uDE80 Initializing postgresdk configuration...
482
+ `);
483
+ const configPath = resolve(process.cwd(), "postgresdk.config.ts");
484
+ if (existsSync(configPath)) {
485
+ console.error("❌ Error: postgresdk.config.ts already exists");
486
+ console.log(" To reinitialize, please remove or rename the existing file first.");
487
+ process.exit(1);
488
+ }
489
+ const envPath = resolve(process.cwd(), ".env");
490
+ const hasEnv = existsSync(envPath);
491
+ try {
492
+ writeFileSync(configPath, CONFIG_TEMPLATE, "utf-8");
493
+ console.log("✅ Created postgresdk.config.ts");
494
+ console.log(`
495
+ \uD83D\uDCDD Next steps:`);
496
+ console.log(" 1. Edit postgresdk.config.ts with your database connection");
497
+ if (!hasEnv) {
498
+ console.log(" 2. Consider creating a .env file for sensitive values:");
499
+ console.log(" DATABASE_URL=postgres://user:pass@localhost:5432/mydb");
500
+ console.log(" API_KEY=your-secret-key");
501
+ console.log(" JWT_SECRET=your-jwt-secret");
502
+ }
503
+ console.log(" 3. Run 'postgresdk generate' to create your SDK");
504
+ console.log(`
505
+ \uD83D\uDCA1 Tip: The config file has detailed comments for all options.`);
506
+ console.log(" Uncomment the options you want to customize.");
507
+ } catch (error) {
508
+ console.error("❌ Error creating config file:", error);
509
+ process.exit(1);
510
+ }
511
+ }
512
+ var CONFIG_TEMPLATE = `/**
513
+ * PostgreSDK Configuration
514
+ *
515
+ * This file configures how postgresdk generates your SDK.
516
+ * Environment variables are automatically loaded from .env files.
517
+ */
518
+
519
+ export default {
520
+ // ========== DATABASE CONNECTION (Required) ==========
521
+
522
+ /**
523
+ * PostgreSQL connection string
524
+ * Format: postgres://user:password@host:port/database
525
+ */
526
+ connectionString: process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb",
527
+
528
+ // ========== BASIC OPTIONS ==========
529
+
530
+ /**
531
+ * Database schema to introspect
532
+ * @default "public"
533
+ */
534
+ // schema: "public",
535
+
536
+ /**
537
+ * Output directory for server-side code (routes, validators, etc.)
538
+ * @default "./generated/server"
539
+ */
540
+ // outServer: "./generated/server",
541
+
542
+ /**
543
+ * Output directory for client SDK
544
+ * @default "./generated/client"
545
+ */
546
+ // outClient: "./generated/client",
547
+
548
+ // ========== ADVANCED OPTIONS ==========
549
+
550
+ /**
551
+ * Column name for soft deletes. When set, DELETE operations will update
552
+ * this column instead of removing rows.
553
+ * @default null (hard deletes)
554
+ * @example "deleted_at"
555
+ */
556
+ // softDeleteColumn: null,
557
+
558
+ /**
559
+ * Maximum depth for nested relationship includes to prevent infinite loops
560
+ * @default 3
561
+ */
562
+ // includeDepthLimit: 3,
563
+
564
+ /**
565
+ * How to handle date/timestamp columns in TypeScript
566
+ * - "date": Use JavaScript Date objects
567
+ * - "string": Use ISO 8601 strings
568
+ * @default "date"
569
+ */
570
+ // dateType: "date",
571
+
572
+ // ========== AUTHENTICATION ==========
573
+
574
+ /**
575
+ * Authentication configuration for your API
576
+ *
577
+ * Simple syntax examples:
578
+ * auth: { apiKey: process.env.API_KEY }
579
+ * auth: { jwt: process.env.JWT_SECRET }
580
+ *
581
+ * Multiple API keys:
582
+ * auth: { apiKeys: [process.env.KEY1, process.env.KEY2] }
583
+ *
584
+ * Full syntax for advanced options:
585
+ */
586
+ // auth: {
587
+ // // Strategy: "none" | "api-key" | "jwt-hs256"
588
+ // strategy: "none",
589
+ //
590
+ // // For API Key authentication
591
+ // apiKeyHeader: "x-api-key", // Header name for API key
592
+ // apiKeys: [ // List of valid API keys
593
+ // process.env.API_KEY_1,
594
+ // process.env.API_KEY_2,
595
+ // ],
596
+ //
597
+ // // For JWT (HS256) authentication
598
+ // jwt: {
599
+ // sharedSecret: process.env.JWT_SECRET, // Secret for signing/verifying
600
+ // issuer: "my-app", // Optional: validate 'iss' claim
601
+ // audience: "my-users", // Optional: validate 'aud' claim
602
+ // }
603
+ // },
604
+
605
+ // ========== SDK DISTRIBUTION (Pull Configuration) ==========
606
+
607
+ /**
608
+ * Configuration for pulling SDK from a remote API
609
+ * Used when running 'postgresdk pull' command
610
+ */
611
+ // pull: {
612
+ // from: "https://api.myapp.com", // API URL to pull SDK from
613
+ // output: "./src/sdk", // Local directory for pulled SDK
614
+ // token: process.env.API_TOKEN, // Optional authentication token
615
+ // },
616
+ };
617
+ `;
618
+ var init_cli_init = () => {};
619
+
620
+ // src/cli-pull.ts
621
+ var exports_cli_pull = {};
622
+ __export(exports_cli_pull, {
623
+ pullCommand: () => pullCommand
624
+ });
625
+ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
626
+ import { join as join2, dirname as dirname2, resolve as resolve2 } from "path";
627
+ import { existsSync as existsSync2 } from "fs";
628
+ import { pathToFileURL as pathToFileURL2 } from "url";
629
+ async function pullCommand(args) {
630
+ let configPath = "postgresdk.config.ts";
631
+ const configIndex = args.findIndex((a) => a === "-c" || a === "--config");
632
+ if (configIndex !== -1 && args[configIndex + 1]) {
633
+ configPath = args[configIndex + 1];
634
+ }
635
+ let fileConfig = {};
636
+ const fullConfigPath = resolve2(process.cwd(), configPath);
637
+ if (existsSync2(fullConfigPath)) {
638
+ console.log(`\uD83D\uDCCB Loading ${configPath}`);
639
+ try {
640
+ const configUrl = pathToFileURL2(fullConfigPath).href;
641
+ const module = await import(configUrl);
642
+ const config2 = module.default || module;
643
+ if (config2.pull) {
644
+ fileConfig = config2.pull;
645
+ }
646
+ } catch (err) {
647
+ console.error("⚠️ Failed to load config file:", err);
648
+ }
649
+ }
650
+ const cliConfig = {
651
+ from: args.find((a) => a.startsWith("--from="))?.split("=")[1],
652
+ output: args.find((a) => a.startsWith("--output="))?.split("=")[1],
653
+ token: args.find((a) => a.startsWith("--token="))?.split("=")[1]
654
+ };
655
+ const config = {
656
+ output: "./src/sdk",
657
+ ...fileConfig,
658
+ ...Object.fromEntries(Object.entries(cliConfig).filter(([_, v]) => v !== undefined))
659
+ };
660
+ if (!config.from) {
661
+ console.error("❌ Missing API URL. Specify via --from or in postgresdk.config.ts");
662
+ console.error(`
663
+ Example config file:`);
664
+ console.error(`export default {
665
+ pull: {
666
+ from: "https://api.company.com",
667
+ output: "./src/sdk"
668
+ }
669
+ }`);
670
+ process.exit(1);
671
+ }
672
+ console.log(`\uD83D\uDD04 Pulling SDK from ${config.from}`);
673
+ console.log(`\uD83D\uDCC1 Output directory: ${config.output}`);
674
+ try {
675
+ const headers = config.token ? { Authorization: `Bearer ${config.token}` } : {};
676
+ const manifestRes = await fetch(`${config.from}/sdk/manifest`, { headers });
677
+ if (!manifestRes.ok) {
678
+ throw new Error(`Failed to fetch SDK manifest: ${manifestRes.status} ${manifestRes.statusText}`);
679
+ }
680
+ const manifest = await manifestRes.json();
681
+ console.log(`\uD83D\uDCE6 SDK version: ${manifest.version}`);
682
+ console.log(`\uD83D\uDCC5 Generated: ${manifest.generated}`);
683
+ console.log(`\uD83D\uDCC4 Files: ${manifest.files.length}`);
684
+ const sdkRes = await fetch(`${config.from}/sdk/download`, { headers });
685
+ if (!sdkRes.ok) {
686
+ throw new Error(`Failed to download SDK: ${sdkRes.status} ${sdkRes.statusText}`);
687
+ }
688
+ const sdk = await sdkRes.json();
689
+ for (const [path, content] of Object.entries(sdk.files)) {
690
+ const fullPath = join2(config.output, path);
691
+ await mkdir2(dirname2(fullPath), { recursive: true });
692
+ await writeFile2(fullPath, content, "utf-8");
693
+ console.log(` ✓ ${path}`);
694
+ }
695
+ await writeFile2(join2(config.output, ".postgresdk.json"), JSON.stringify({
696
+ version: sdk.version,
697
+ generated: sdk.generated,
698
+ pulledFrom: config.from,
699
+ pulledAt: new Date().toISOString()
700
+ }, null, 2));
701
+ console.log(`✅ SDK pulled successfully to ${config.output}`);
702
+ } catch (err) {
703
+ console.error(`❌ Pull failed:`, err);
704
+ process.exit(1);
705
+ }
706
+ }
707
+ var init_cli_pull = () => {};
708
+
463
709
  // src/index.ts
464
710
  var import_config = __toESM(require_config(), 1);
465
711
  import { join } from "node:path";
@@ -958,13 +1204,116 @@ function emitClient(table) {
958
1204
  const pkType = hasCompositePk ? `{ ${safePk.map((c) => `${c}: string`).join("; ")} }` : `string`;
959
1205
  const pkPathExpr = hasCompositePk ? safePk.map((c) => `pk.${c}`).join(` + "/" + `) : `pk`;
960
1206
  return `/* Generated. Do not edit. */
1207
+ import { BaseClient } from "./base-client";
961
1208
  import type { ${Type}IncludeSpec } from "./include-spec";
962
1209
  import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}";
963
1210
 
964
- type HeaderMap = Record<string, string>;
965
- type AuthHeadersProvider = () => Promise<HeaderMap> | HeaderMap;
1211
+ /**
1212
+ * Client for ${table.name} table operations
1213
+ */
1214
+ export class ${Type}Client extends BaseClient {
1215
+ private readonly resource = "/v1/${table.name}";
1216
+
1217
+ async create(data: Insert${Type}): Promise<Select${Type}> {
1218
+ return this.post<Select${Type}>(this.resource, data);
1219
+ }
966
1220
 
967
- type AuthConfig =
1221
+ async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
1222
+ const path = ${pkPathExpr};
1223
+ return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
1224
+ }
1225
+
1226
+ async list(params?: {
1227
+ include?: ${Type}IncludeSpec;
1228
+ limit?: number;
1229
+ offset?: number;
1230
+ where?: any;
1231
+ orderBy?: string;
1232
+ order?: "asc" | "desc";
1233
+ }): Promise<Select${Type}[]> {
1234
+ return this.post<Select${Type}[]>(\`\${this.resource}/list\`, params ?? {});
1235
+ }
1236
+
1237
+ async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
1238
+ const path = ${pkPathExpr};
1239
+ return this.patch<Select${Type} | null>(\`\${this.resource}/\${path}\`, patch);
1240
+ }
1241
+
1242
+ async delete(pk: ${pkType}): Promise<Select${Type} | null> {
1243
+ const path = ${pkPathExpr};
1244
+ return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
1245
+ }
1246
+ }
1247
+ `;
1248
+ }
1249
+ function emitClientIndex(tables) {
1250
+ let out = `/* Generated. Do not edit. */
1251
+ `;
1252
+ out += `import { BaseClient, AuthConfig } from "./base-client";
1253
+ `;
1254
+ for (const t of tables) {
1255
+ out += `import { ${pascal(t.name)}Client } from "./${t.name}";
1256
+ `;
1257
+ }
1258
+ out += `
1259
+ // Re-export auth types for convenience
1260
+ `;
1261
+ out += `export type { AuthConfig as SDKAuth, AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client";
1262
+
1263
+ `;
1264
+ out += `/**
1265
+ `;
1266
+ out += ` * Main SDK class that provides access to all table clients
1267
+ `;
1268
+ out += ` */
1269
+ `;
1270
+ out += `export class SDK {
1271
+ `;
1272
+ for (const t of tables) {
1273
+ out += ` public ${t.name}: ${pascal(t.name)}Client;
1274
+ `;
1275
+ }
1276
+ out += `
1277
+ constructor(cfg: { baseUrl: string; fetch?: typeof fetch; auth?: AuthConfig }) {
1278
+ `;
1279
+ out += ` const f = cfg.fetch ?? fetch;
1280
+ `;
1281
+ for (const t of tables) {
1282
+ out += ` this.${t.name} = new ${pascal(t.name)}Client(cfg.baseUrl, f, cfg.auth);
1283
+ `;
1284
+ }
1285
+ out += ` }
1286
+ `;
1287
+ out += `}
1288
+
1289
+ `;
1290
+ out += `// Export individual table clients
1291
+ `;
1292
+ for (const t of tables) {
1293
+ out += `export { ${pascal(t.name)}Client } from "./${t.name}";
1294
+ `;
1295
+ }
1296
+ out += `
1297
+ // Export base client for custom extensions
1298
+ `;
1299
+ out += `export { BaseClient } from "./base-client";
1300
+ `;
1301
+ out += `
1302
+ // Export include specifications
1303
+ `;
1304
+ out += `export * from "./include-spec";
1305
+ `;
1306
+ return out;
1307
+ }
1308
+
1309
+ // src/emit-base-client.ts
1310
+ function emitBaseClient() {
1311
+ return `/* Generated. Do not edit. */
1312
+
1313
+ export type HeaderMap = Record<string, string>;
1314
+ export type AuthHeadersProvider = () => Promise<HeaderMap> | HeaderMap;
1315
+
1316
+ export type AuthConfig =
968
1317
  | AuthHeadersProvider
969
1318
  | {
970
1319
  apiKey?: string;
@@ -976,14 +1325,18 @@ type AuthConfig =
976
1325
  headers?: AuthHeadersProvider;
977
1326
  };
978
1327
 
979
- export class ${Type}Client {
1328
+ /**
1329
+ * Base client class with shared authentication and request handling logic.
1330
+ * All table-specific clients extend this class.
1331
+ */
1332
+ export abstract class BaseClient {
980
1333
  constructor(
981
- private baseUrl: string,
982
- private fetchFn: typeof fetch = fetch,
983
- private auth?: AuthConfig
1334
+ protected baseUrl: string,
1335
+ protected fetchFn: typeof fetch = fetch,
1336
+ protected auth?: AuthConfig
984
1337
  ) {}
985
1338
 
986
- private async authHeaders(): Promise<HeaderMap> {
1339
+ protected async authHeaders(): Promise<HeaderMap> {
987
1340
  if (!this.auth) return {};
988
1341
  if (typeof this.auth === "function") {
989
1342
  const h = await this.auth();
@@ -1009,129 +1362,91 @@ export class ${Type}Client {
1009
1362
  return out;
1010
1363
  }
1011
1364
 
1012
- private async headers(json = false) {
1365
+ protected async headers(json = false): Promise<HeaderMap> {
1013
1366
  const extra = await this.authHeaders();
1014
1367
  return json ? { "Content-Type": "application/json", ...extra } : extra;
1015
1368
  }
1016
1369
 
1017
- private async okOrThrow(res: Response, action: string) {
1370
+ protected async okOrThrow(res: Response, action: string, entity: string): Promise<void> {
1018
1371
  if (!res.ok) {
1019
1372
  let detail = "";
1020
1373
  try { detail = await res.text(); } catch {}
1021
- throw new Error(\`\${action} ${table.name} failed: \${res.status} \${detail}\`);
1374
+ throw new Error(\`\${action} \${entity} failed: \${res.status} \${detail}\`);
1022
1375
  }
1023
1376
  }
1024
1377
 
1025
- async create(data: Insert${Type}): Promise<Select${Type}> {
1026
- const res = await this.fetchFn(\`\${this.baseUrl}/v1/${table.name}\`, {
1378
+ /**
1379
+ * Make a POST request
1380
+ */
1381
+ protected async post<T>(path: string, body?: any): Promise<T> {
1382
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1027
1383
  method: "POST",
1028
1384
  headers: await this.headers(true),
1029
- body: JSON.stringify(data),
1385
+ body: JSON.stringify(body),
1030
1386
  });
1031
- await this.okOrThrow(res, "create");
1032
- return (await res.json()) as Select${Type};
1387
+
1388
+ // Handle 404 specially for operations that might return null
1389
+ if (res.status === 404) {
1390
+ return null as T;
1391
+ }
1392
+
1393
+ await this.okOrThrow(res, "POST", path);
1394
+ return (await res.json()) as T;
1033
1395
  }
1034
1396
 
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}\`, {
1397
+ /**
1398
+ * Make a GET request
1399
+ */
1400
+ protected async get<T>(path: string): Promise<T> {
1401
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1038
1402
  headers: await this.headers(),
1039
1403
  });
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}[];
1404
+
1405
+ if (res.status === 404) {
1406
+ return null as T;
1407
+ }
1408
+
1409
+ await this.okOrThrow(res, "GET", path);
1410
+ return (await res.json()) as T;
1053
1411
  }
1054
1412
 
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}\`, {
1413
+ /**
1414
+ * Make a PATCH request
1415
+ */
1416
+ protected async patch<T>(path: string, body?: any): Promise<T> {
1417
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1058
1418
  method: "PATCH",
1059
1419
  headers: await this.headers(true),
1060
- body: JSON.stringify(patch),
1420
+ body: JSON.stringify(body),
1061
1421
  });
1062
- if (res.status === 404) return null;
1063
- await this.okOrThrow(res, "update");
1064
- return (await res.json()) as Select${Type};
1422
+
1423
+ if (res.status === 404) {
1424
+ return null as T;
1425
+ }
1426
+
1427
+ await this.okOrThrow(res, "PATCH", path);
1428
+ return (await res.json()) as T;
1065
1429
  }
1066
1430
 
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}\`, {
1431
+ /**
1432
+ * Make a DELETE request
1433
+ */
1434
+ protected async del<T>(path: string): Promise<T> {
1435
+ const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
1070
1436
  method: "DELETE",
1071
1437
  headers: await this.headers(),
1072
1438
  });
1073
- if (res.status === 404) return null;
1074
- await this.okOrThrow(res, "delete");
1075
- return (await res.json()) as Select${Type};
1439
+
1440
+ if (res.status === 404) {
1441
+ return null as T;
1442
+ }
1443
+
1444
+ await this.okOrThrow(res, "DELETE", path);
1445
+ return (await res.json()) as T;
1076
1446
  }
1077
1447
  }
1078
1448
  `;
1079
1449
  }
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
1450
 
1136
1451
  // src/emit-include-loader.ts
1137
1452
  function emitIncludeLoader(graph, model, maxDepth) {
@@ -1679,6 +1994,7 @@ function emitRouter(tables, hasAuth) {
1679
1994
  `);
1680
1995
  return `/* Generated. Do not edit. */
1681
1996
  import { Hono } from "hono";
1997
+ import { SDK_MANIFEST } from "./sdk-bundle";
1682
1998
  ${imports}
1683
1999
  ${hasAuth ? `export { authMiddleware } from "./auth";` : ""}
1684
2000
 
@@ -1706,7 +2022,34 @@ export function createRouter(
1706
2022
  deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }
1707
2023
  ): Hono {
1708
2024
  const router = new Hono();
2025
+
2026
+ // Register table routes
1709
2027
  ${registrations}
2028
+
2029
+ // SDK distribution endpoints
2030
+ router.get("/sdk/manifest", (c) => {
2031
+ return c.json({
2032
+ version: SDK_MANIFEST.version,
2033
+ generated: SDK_MANIFEST.generated,
2034
+ files: Object.keys(SDK_MANIFEST.files)
2035
+ });
2036
+ });
2037
+
2038
+ router.get("/sdk/download", (c) => {
2039
+ return c.json(SDK_MANIFEST);
2040
+ });
2041
+
2042
+ router.get("/sdk/files/:path{.*}", (c) => {
2043
+ const path = c.req.param("path");
2044
+ const content = SDK_MANIFEST.files[path];
2045
+ if (!content) {
2046
+ return c.text("File not found", 404);
2047
+ }
2048
+ return c.text(content, 200, {
2049
+ "Content-Type": "text/plain; charset=utf-8"
2050
+ });
2051
+ });
2052
+
1710
2053
  return router;
1711
2054
  }
1712
2055
 
@@ -1740,6 +2083,29 @@ export * from "./include-spec";
1740
2083
  `;
1741
2084
  }
1742
2085
 
2086
+ // src/emit-sdk-bundle.ts
2087
+ function emitSdkBundle(clientFiles) {
2088
+ const files = {};
2089
+ for (const file of clientFiles) {
2090
+ const parts = file.path.split("/");
2091
+ const clientIndex = parts.lastIndexOf("client");
2092
+ if (clientIndex >= 0 && clientIndex < parts.length - 1) {
2093
+ const relativePath = parts.slice(clientIndex + 1).join("/");
2094
+ files[relativePath] = file.content;
2095
+ }
2096
+ }
2097
+ const version = `1.0.0`;
2098
+ const generated = new Date().toISOString();
2099
+ return `/* Generated. Do not edit. */
2100
+
2101
+ export const SDK_MANIFEST = {
2102
+ version: "${version}",
2103
+ generated: "${generated}",
2104
+ files: ${JSON.stringify(files, null, 2)}
2105
+ };
2106
+ `;
2107
+ }
2108
+
1743
2109
  // src/types.ts
1744
2110
  function normalizeAuthConfig(input) {
1745
2111
  if (!input)
@@ -1804,6 +2170,7 @@ async function generate(configPath) {
1804
2170
  const includeSpec = emitIncludeSpec(graph);
1805
2171
  files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
1806
2172
  files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
2173
+ files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
1807
2174
  files.push({
1808
2175
  path: join(serverDir, "include-builder.ts"),
1809
2176
  content: emitIncludeBuilder(graph, cfg.includeDepthLimit || 3)
@@ -1845,6 +2212,11 @@ async function generate(configPath) {
1845
2212
  path: join(serverDir, "router.ts"),
1846
2213
  content: emitRouter(Object.values(model.tables), !!normalizedAuth?.strategy && normalizedAuth.strategy !== "none")
1847
2214
  });
2215
+ const clientFiles = files.filter((f) => f.path.includes(clientDir));
2216
+ files.push({
2217
+ path: join(serverDir, "sdk-bundle.ts"),
2218
+ content: emitSdkBundle(clientFiles)
2219
+ });
1848
2220
  console.log("✍️ Writing files...");
1849
2221
  await writeFiles(files);
1850
2222
  console.log(`✅ Generated ${files.length} files`);
@@ -1854,41 +2226,75 @@ async function generate(configPath) {
1854
2226
 
1855
2227
  // src/cli.ts
1856
2228
  var import_config2 = __toESM(require_config(), 1);
1857
- import { resolve } from "node:path";
2229
+ import { resolve as resolve3 } from "node:path";
1858
2230
  import { readFileSync } from "node:fs";
1859
2231
  import { fileURLToPath } from "node:url";
1860
- import { dirname as dirname2, join as join2 } from "node:path";
2232
+ import { dirname as dirname3, join as join3 } from "node:path";
1861
2233
  var __filename2 = fileURLToPath(import.meta.url);
1862
- var __dirname2 = dirname2(__filename2);
1863
- var packageJson = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
2234
+ var __dirname2 = dirname3(__filename2);
2235
+ var packageJson = JSON.parse(readFileSync(join3(__dirname2, "../package.json"), "utf-8"));
1864
2236
  var VERSION = packageJson.version;
1865
2237
  var args = process.argv.slice(2);
1866
- if (args.includes("--version") || args.includes("-v")) {
2238
+ var command = args[0];
2239
+ if (args.includes("--version") || args.includes("-v") || command === "version") {
1867
2240
  console.log(`postgresdk v${VERSION}`);
1868
2241
  process.exit(0);
1869
2242
  }
1870
- if (args.includes("--help") || args.includes("-h")) {
2243
+ if (args.includes("--help") || args.includes("-h") || command === "help" || !command) {
1871
2244
  console.log(`
1872
2245
  postgresdk - Generate typed SDK from PostgreSQL
1873
2246
 
1874
2247
  Usage:
1875
- postgresdk [options]
2248
+ postgresdk <command> [options]
2249
+
2250
+ Commands:
2251
+ init Create a postgresdk.config.ts file
2252
+ generate Generate SDK from database
2253
+ pull Pull SDK from API endpoint
2254
+ version Show version
2255
+ help Show help
1876
2256
 
1877
- Options:
2257
+ Init Options:
2258
+ (no options)
2259
+
2260
+ Generate Options:
1878
2261
  -c, --config <path> Path to config file (default: postgresdk.config.ts)
1879
- -v, --version Show version
1880
- -h, --help Show help
2262
+
2263
+ Pull Options:
2264
+ --from <url> API URL to pull SDK from
2265
+ --output <path> Output directory (default: ./src/sdk)
2266
+ --token <token> Authentication token
2267
+ -c, --config <path> Path to config file with pull settings
2268
+
2269
+ Examples:
2270
+ postgresdk init # Create config file
2271
+ postgresdk generate # Generate using postgresdk.config.ts
2272
+ postgresdk generate -c custom.config.ts
2273
+ postgresdk pull --from=https://api.com --output=./src/sdk
2274
+ postgresdk pull # Pull using config file
1881
2275
  `);
1882
2276
  process.exit(0);
1883
2277
  }
1884
- var configPath = "postgresdk.config.ts";
1885
- var configIndex = args.findIndex((a) => a === "-c" || a === "--config");
1886
- if (configIndex !== -1 && args[configIndex + 1]) {
1887
- configPath = args[configIndex + 1];
1888
- }
1889
- try {
1890
- await generate(resolve(process.cwd(), configPath));
1891
- } catch (err) {
1892
- console.error("❌ Generation failed:", err);
2278
+ if (command === "init") {
2279
+ const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_cli_init(), exports_cli_init));
2280
+ await initCommand2(args.slice(1));
2281
+ } else if (command === "generate") {
2282
+ let configPath = "postgresdk.config.ts";
2283
+ const configIndex = args.findIndex((a) => a === "-c" || a === "--config");
2284
+ if (configIndex !== -1 && args[configIndex + 1]) {
2285
+ configPath = args[configIndex + 1];
2286
+ }
2287
+ try {
2288
+ await generate(resolve3(process.cwd(), configPath));
2289
+ } catch (err) {
2290
+ console.error("❌ Generation failed:", err);
2291
+ process.exit(1);
2292
+ }
2293
+ } else if (command === "pull") {
2294
+ const { pullCommand: pullCommand2 } = await Promise.resolve().then(() => (init_cli_pull(), exports_cli_pull));
2295
+ await pullCommand2(args.slice(1));
2296
+ } else {
2297
+ console.error(`❌ Unknown command: ${command}`);
2298
+ console.error(`Run 'postgresdk help' for usage information`);
1893
2299
  process.exit(1);
1894
2300
  }