postgresdk 0.1.2-alpha.4 → 0.2.1-alpha.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 CHANGED
@@ -35,7 +35,7 @@ CREATE TABLE book_tags (
35
35
  Run one command:
36
36
 
37
37
  ```bash
38
- npx postgresdk
38
+ npx postgresdk generate
39
39
  ```
40
40
 
41
41
  Get a complete, type-safe SDK with:
@@ -140,36 +140,43 @@ All from your existing database schema. No manual coding required.
140
140
  - 🔐 **Built-in Auth** - API key and JWT authentication with zero configuration
141
141
  - 🎯 **Zero Config** - Works out of the box with sensible defaults
142
142
  - 🏗️ **Framework Ready** - Server routes built for Hono, client SDK works anywhere
143
- - 📦 **Lightweight** - Minimal dependencies, optimized for production
143
+ - 📦 **Lightweight** - Minimal dependencies, optimized bundle size with shared BaseClient
144
+ - 🔄 **SDK Distribution** - Built-in SDK bundling and pull mechanism for easy client distribution
144
145
 
145
146
  ## Installation
146
147
 
147
148
  ```bash
148
149
  npm install -g postgresdk
149
150
  # or
150
- npx postgresdk
151
+ npx postgresdk generate
151
152
  ```
152
153
 
153
154
  ## Quick Start
154
155
 
155
- 1. Create a configuration file `postgresdk.config.ts`:
156
+ 1. Initialize your project:
157
+
158
+ ```bash
159
+ npx postgresdk init
160
+ ```
161
+
162
+ This creates a `postgresdk.config.ts` file with all available options documented.
163
+
164
+ 2. Edit the configuration file with your database connection:
156
165
 
157
166
  ```typescript
158
167
  export default {
159
- connectionString: "postgres://user:pass@localhost:5432/mydb",
160
- schema: "public",
161
- outServer: "./generated/server",
162
- outClient: "./generated/client",
168
+ connectionString: process.env.DATABASE_URL || "postgres://user:pass@localhost:5432/mydb",
169
+ // Uncomment and customize other options as needed
163
170
  };
164
171
  ```
165
172
 
166
- 2. Run the generator:
173
+ 3. Run the generator:
167
174
 
168
175
  ```bash
169
- postgresdk
176
+ postgresdk generate
170
177
  ```
171
178
 
172
- 3. Use the generated SDK:
179
+ 4. Use the generated SDK:
173
180
 
174
181
  ```typescript
175
182
  // Server (Hono)
@@ -212,19 +219,19 @@ export default {
212
219
  includeDepthLimit: 3, // Max depth for nested includes
213
220
  dateType: "date", // "date" | "string" - How to handle timestamps
214
221
 
215
- // Authentication (optional)
222
+ // Authentication (optional) - simplified syntax
216
223
  auth: {
224
+ apiKey: process.env.API_KEY, // Simple API key auth
225
+ // OR
226
+ jwt: process.env.JWT_SECRET, // Simple JWT auth
227
+ // OR full syntax for advanced options:
217
228
  strategy: "none" | "api-key" | "jwt-hs256", // Default: "none"
218
-
219
- // For API key auth
220
- apiKeyHeader: "x-api-key", // Header name for API key
221
- apiKeys: ["key1", "key2"], // Array of valid keys
222
-
223
- // For JWT auth (HS256)
229
+ apiKeyHeader: "x-api-key", // Custom header name
230
+ apiKeys: ["key1", "key2"], // Multiple valid keys
224
231
  jwt: {
225
- sharedSecret: "your-secret", // Shared secret for HS256
226
- issuer: "your-app", // Optional: validate issuer claim
227
- audience: "your-audience" // Optional: validate audience claim
232
+ sharedSecret: "your-secret", // Shared secret for HS256
233
+ issuer: "your-app", // Optional: validate issuer claim
234
+ audience: "your-audience" // Optional: validate audience claim
228
235
  }
229
236
  }
230
237
  };
@@ -348,11 +355,11 @@ export default {
348
355
  ### API Key Authentication
349
356
 
350
357
  ```typescript
351
- // postgresdk.config.ts - Simplified syntax
358
+ // postgresdk.config.ts - Simple syntax (recommended)
352
359
  export default {
353
360
  connectionString: "...",
354
361
  auth: {
355
- apiKey: "your-api-key" // Single key shorthand
362
+ apiKey: process.env.API_KEY // Single key shorthand
356
363
  }
357
364
  };
358
365
 
@@ -360,50 +367,45 @@ export default {
360
367
  export default {
361
368
  connectionString: "...",
362
369
  auth: {
363
- apiKeys: ["key1", "key2", "key3"]
370
+ apiKeys: [process.env.API_KEY_1, process.env.API_KEY_2]
364
371
  }
365
372
  };
366
373
 
367
- // Or full syntax with custom header
374
+ // Full syntax with custom header (optional)
368
375
  export default {
369
376
  connectionString: "...",
370
377
  auth: {
371
378
  strategy: "api-key",
372
- apiKeyHeader: "x-api-key", // Optional, defaults to "x-api-key"
373
- apiKeys: [
374
- "your-api-key-1",
375
- "your-api-key-2",
376
- // Can also use environment variables
377
- "env:API_KEYS" // Reads comma-separated keys from process.env.API_KEYS
378
- ]
379
+ apiKeyHeader: "x-custom-key", // Default: "x-api-key"
380
+ apiKeys: ["key1", "key2"]
379
381
  }
380
382
  };
381
383
 
382
384
  // Client SDK usage
383
385
  const sdk = new SDK({
384
386
  baseUrl: "http://localhost:3000",
385
- auth: { apiKey: "your-api-key-1" }
387
+ auth: { apiKey: process.env.API_KEY }
386
388
  });
387
389
  ```
388
390
 
389
391
  ### JWT Authentication (HS256)
390
392
 
391
393
  ```typescript
392
- // postgresdk.config.ts - Simplified syntax
394
+ // postgresdk.config.ts - Simple syntax (recommended)
393
395
  export default {
394
396
  connectionString: "...",
395
397
  auth: {
396
- jwt: "your-secret-key" // Shared secret shorthand
398
+ jwt: process.env.JWT_SECRET // Shared secret shorthand
397
399
  }
398
400
  };
399
401
 
400
- // Or full syntax with issuer/audience validation
402
+ // Full syntax with issuer/audience validation (optional)
401
403
  export default {
402
404
  connectionString: "...",
403
405
  auth: {
404
406
  strategy: "jwt-hs256",
405
407
  jwt: {
406
- sharedSecret: process.env.JWT_SECRET || "your-secret-key",
408
+ sharedSecret: process.env.JWT_SECRET,
407
409
  issuer: "my-app", // Optional: validates 'iss' claim
408
410
  audience: "my-users" // Optional: validates 'aud' claim
409
411
  }
@@ -772,15 +774,114 @@ process.on("SIGTERM", async () => {
772
774
  });
773
775
  ```
774
776
 
775
- ## CLI Options
777
+ ## SDK Distribution
778
+
779
+ postgresdk makes it easy to distribute your generated SDK to client applications. When you generate your SDK, it's automatically bundled and served through your API, allowing client apps to pull the latest SDK directly.
780
+
781
+ ### Publishing Your SDK
782
+
783
+ When you run `postgresdk generate`, the SDK is automatically:
784
+ 1. Generated as TypeScript files for both server and client
785
+ 2. Bundled and made available through API endpoints
786
+ 3. Ready to be pulled by client applications
787
+
788
+ Your API automatically serves the SDK at these endpoints:
789
+ - `GET /sdk/manifest` - SDK metadata and file list
790
+ - `GET /sdk/download` - Download all SDK files as JSON
791
+ - `GET /sdk/files/:path` - Download individual SDK files
792
+
793
+ ### Pulling the SDK in Client Apps
794
+
795
+ Client applications can pull your SDK using the `postgresdk pull` command:
796
+
797
+ ```bash
798
+ # Install postgresdk in your client app
799
+ npm install -D postgresdk
800
+
801
+ # Pull the SDK from your API
802
+ postgresdk pull --from=https://api.myapp.com --output=./src/sdk
803
+
804
+ # Or with authentication
805
+ postgresdk pull --from=https://api.myapp.com --output=./src/sdk --token=your-token
806
+ ```
807
+
808
+ ### Configuration-based Pull
809
+
810
+ Create a `postgresdk.config.ts` in your client app:
811
+
812
+ ```typescript
813
+ export default {
814
+ pull: {
815
+ from: "https://api.myapp.com",
816
+ output: "./src/sdk",
817
+ token: process.env.API_TOKEN // Optional auth
818
+ }
819
+ };
820
+ ```
821
+
822
+ Then simply run:
823
+ ```bash
824
+ postgresdk pull
825
+ ```
826
+
827
+ ### Automated SDK Updates
828
+
829
+ You can automate SDK updates in your client app's build process:
830
+
831
+ ```json
832
+ // package.json
833
+ {
834
+ "scripts": {
835
+ "prebuild": "postgresdk pull",
836
+ "build": "tsc"
837
+ }
838
+ }
839
+ ```
840
+
841
+ ### SDK Versioning
842
+
843
+ The pulled SDK includes metadata about when it was generated and from where:
844
+
845
+ ```typescript
846
+ // .postgresdk.json (auto-generated)
847
+ {
848
+ "version": "1.0.0",
849
+ "generated": "2024-01-15T10:30:00Z",
850
+ "pulledFrom": "https://api.myapp.com",
851
+ "pulledAt": "2024-01-15T11:00:00Z"
852
+ }
853
+ ```
854
+
855
+ ## CLI Commands
776
856
 
777
857
  ```bash
778
- postgresdk [options]
858
+ postgresdk <command> [options]
859
+
860
+ Commands:
861
+ init Create a postgresdk.config.ts file with all options
862
+ generate Generate SDK from database
863
+ pull Pull SDK from API endpoint
864
+ version Show version
865
+ help Show help
779
866
 
780
- Options:
867
+ Init Options:
868
+ (no options) Creates postgresdk.config.ts in current directory
869
+
870
+ Generate Options:
781
871
  -c, --config <path> Path to config file (default: postgresdk.config.ts)
782
- -v, --version Show version
783
- -h, --help Show help
872
+
873
+ Pull Options:
874
+ --from <url> API URL to pull SDK from
875
+ --output <path> Output directory (default: ./src/sdk)
876
+ --token <token> Authentication token
877
+ -c, --config <path> Path to config file with pull settings
878
+
879
+ Examples:
880
+ postgresdk init # Create config file
881
+ postgresdk generate # Generate using default config
882
+ postgresdk generate -c custom.config.ts
883
+ postgresdk pull --from=https://api.com --output=./src/sdk
884
+ postgresdk pull -c client.config.ts # Pull using config file
784
885
  ```
785
886
 
786
887
  ## How It Works
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function initCommand(args: string[]): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare function pullCommand(args: string[]): Promise<void>;
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";
@@ -1748,6 +1994,7 @@ function emitRouter(tables, hasAuth) {
1748
1994
  `);
1749
1995
  return `/* Generated. Do not edit. */
1750
1996
  import { Hono } from "hono";
1997
+ import { SDK_MANIFEST } from "./sdk-bundle";
1751
1998
  ${imports}
1752
1999
  ${hasAuth ? `export { authMiddleware } from "./auth";` : ""}
1753
2000
 
@@ -1775,7 +2022,34 @@ export function createRouter(
1775
2022
  deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }
1776
2023
  ): Hono {
1777
2024
  const router = new Hono();
2025
+
2026
+ // Register table routes
1778
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
+
1779
2053
  return router;
1780
2054
  }
1781
2055
 
@@ -1809,6 +2083,29 @@ export * from "./include-spec";
1809
2083
  `;
1810
2084
  }
1811
2085
 
2086
+ // src/emit-sdk-bundle.ts
2087
+ function emitSdkBundle(clientFiles, clientDir) {
2088
+ const files = {};
2089
+ for (const file of clientFiles) {
2090
+ const normalizedClientDir = clientDir.replace(/\\/g, "/");
2091
+ const normalizedFilePath = file.path.replace(/\\/g, "/");
2092
+ if (normalizedFilePath.startsWith(normalizedClientDir)) {
2093
+ const relativePath = normalizedFilePath.substring(normalizedClientDir.length).replace(/^\//, "");
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
+
1812
2109
  // src/types.ts
1813
2110
  function normalizeAuthConfig(input) {
1814
2111
  if (!input)
@@ -1858,7 +2155,12 @@ async function generate(configPath) {
1858
2155
  console.log("\uD83D\uDD17 Building relationship graph...");
1859
2156
  const graph = buildGraph(model);
1860
2157
  const serverDir = cfg.outServer || "./generated/server";
1861
- const clientDir = cfg.outClient || "./generated/client";
2158
+ const originalClientDir = cfg.outClient || "./generated/client";
2159
+ const sameDirectory = serverDir === originalClientDir;
2160
+ let clientDir = originalClientDir;
2161
+ if (sameDirectory) {
2162
+ clientDir = join(originalClientDir, "sdk");
2163
+ }
1862
2164
  const normDateType = cfg.dateType === "string" ? "string" : "date";
1863
2165
  console.log("\uD83D\uDCC1 Creating directories...");
1864
2166
  await ensureDirs([
@@ -1915,50 +2217,91 @@ async function generate(configPath) {
1915
2217
  path: join(serverDir, "router.ts"),
1916
2218
  content: emitRouter(Object.values(model.tables), !!normalizedAuth?.strategy && normalizedAuth.strategy !== "none")
1917
2219
  });
2220
+ const clientFiles = files.filter((f) => {
2221
+ return f.path.includes(clientDir);
2222
+ });
2223
+ files.push({
2224
+ path: join(serverDir, "sdk-bundle.ts"),
2225
+ content: emitSdkBundle(clientFiles, clientDir)
2226
+ });
1918
2227
  console.log("✍️ Writing files...");
1919
2228
  await writeFiles(files);
1920
2229
  console.log(`✅ Generated ${files.length} files`);
1921
2230
  console.log(` Server: ${serverDir}`);
1922
- console.log(` Client: ${clientDir}`);
2231
+ console.log(` Client: ${sameDirectory ? clientDir + " (in sdk subdir due to same output dir)" : clientDir}`);
1923
2232
  }
1924
2233
 
1925
2234
  // src/cli.ts
1926
2235
  var import_config2 = __toESM(require_config(), 1);
1927
- import { resolve } from "node:path";
2236
+ import { resolve as resolve3 } from "node:path";
1928
2237
  import { readFileSync } from "node:fs";
1929
2238
  import { fileURLToPath } from "node:url";
1930
- import { dirname as dirname2, join as join2 } from "node:path";
2239
+ import { dirname as dirname3, join as join3 } from "node:path";
1931
2240
  var __filename2 = fileURLToPath(import.meta.url);
1932
- var __dirname2 = dirname2(__filename2);
1933
- var packageJson = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
2241
+ var __dirname2 = dirname3(__filename2);
2242
+ var packageJson = JSON.parse(readFileSync(join3(__dirname2, "../package.json"), "utf-8"));
1934
2243
  var VERSION = packageJson.version;
1935
2244
  var args = process.argv.slice(2);
1936
- if (args.includes("--version") || args.includes("-v")) {
2245
+ var command = args[0];
2246
+ if (args.includes("--version") || args.includes("-v") || command === "version") {
1937
2247
  console.log(`postgresdk v${VERSION}`);
1938
2248
  process.exit(0);
1939
2249
  }
1940
- if (args.includes("--help") || args.includes("-h")) {
2250
+ if (args.includes("--help") || args.includes("-h") || command === "help" || !command) {
1941
2251
  console.log(`
1942
2252
  postgresdk - Generate typed SDK from PostgreSQL
1943
2253
 
1944
2254
  Usage:
1945
- postgresdk [options]
2255
+ postgresdk <command> [options]
2256
+
2257
+ Commands:
2258
+ init Create a postgresdk.config.ts file
2259
+ generate Generate SDK from database
2260
+ pull Pull SDK from API endpoint
2261
+ version Show version
2262
+ help Show help
2263
+
2264
+ Init Options:
2265
+ (no options)
1946
2266
 
1947
- Options:
2267
+ Generate Options:
1948
2268
  -c, --config <path> Path to config file (default: postgresdk.config.ts)
1949
- -v, --version Show version
1950
- -h, --help Show help
2269
+
2270
+ Pull Options:
2271
+ --from <url> API URL to pull SDK from
2272
+ --output <path> Output directory (default: ./src/sdk)
2273
+ --token <token> Authentication token
2274
+ -c, --config <path> Path to config file with pull settings
2275
+
2276
+ Examples:
2277
+ postgresdk init # Create config file
2278
+ postgresdk generate # Generate using postgresdk.config.ts
2279
+ postgresdk generate -c custom.config.ts
2280
+ postgresdk pull --from=https://api.com --output=./src/sdk
2281
+ postgresdk pull # Pull using config file
1951
2282
  `);
1952
2283
  process.exit(0);
1953
2284
  }
1954
- var configPath = "postgresdk.config.ts";
1955
- var configIndex = args.findIndex((a) => a === "-c" || a === "--config");
1956
- if (configIndex !== -1 && args[configIndex + 1]) {
1957
- configPath = args[configIndex + 1];
1958
- }
1959
- try {
1960
- await generate(resolve(process.cwd(), configPath));
1961
- } catch (err) {
1962
- console.error("❌ Generation failed:", err);
2285
+ if (command === "init") {
2286
+ const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_cli_init(), exports_cli_init));
2287
+ await initCommand2(args.slice(1));
2288
+ } else if (command === "generate") {
2289
+ let configPath = "postgresdk.config.ts";
2290
+ const configIndex = args.findIndex((a) => a === "-c" || a === "--config");
2291
+ if (configIndex !== -1 && args[configIndex + 1]) {
2292
+ configPath = args[configIndex + 1];
2293
+ }
2294
+ try {
2295
+ await generate(resolve3(process.cwd(), configPath));
2296
+ } catch (err) {
2297
+ console.error("❌ Generation failed:", err);
2298
+ process.exit(1);
2299
+ }
2300
+ } else if (command === "pull") {
2301
+ const { pullCommand: pullCommand2 } = await Promise.resolve().then(() => (init_cli_pull(), exports_cli_pull));
2302
+ await pullCommand2(args.slice(1));
2303
+ } else {
2304
+ console.error(`❌ Unknown command: ${command}`);
2305
+ console.error(`Run 'postgresdk help' for usage information`);
1963
2306
  process.exit(1);
1964
2307
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Generates a bundle containing all client SDK files as a single module
3
+ * that can be served by the API
4
+ */
5
+ export declare function emitSdkBundle(clientFiles: {
6
+ path: string;
7
+ content: string;
8
+ }[], clientDir: string): string;
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
@@ -1747,6 +1757,7 @@ function emitRouter(tables, hasAuth) {
1747
1757
  `);
1748
1758
  return `/* Generated. Do not edit. */
1749
1759
  import { Hono } from "hono";
1760
+ import { SDK_MANIFEST } from "./sdk-bundle";
1750
1761
  ${imports}
1751
1762
  ${hasAuth ? `export { authMiddleware } from "./auth";` : ""}
1752
1763
 
@@ -1774,7 +1785,34 @@ export function createRouter(
1774
1785
  deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }
1775
1786
  ): Hono {
1776
1787
  const router = new Hono();
1788
+
1789
+ // Register table routes
1777
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
+
1778
1816
  return router;
1779
1817
  }
1780
1818
 
@@ -1808,6 +1846,29 @@ export * from "./include-spec";
1808
1846
  `;
1809
1847
  }
1810
1848
 
1849
+ // src/emit-sdk-bundle.ts
1850
+ function emitSdkBundle(clientFiles, clientDir) {
1851
+ const files = {};
1852
+ for (const file of clientFiles) {
1853
+ const normalizedClientDir = clientDir.replace(/\\/g, "/");
1854
+ const normalizedFilePath = file.path.replace(/\\/g, "/");
1855
+ if (normalizedFilePath.startsWith(normalizedClientDir)) {
1856
+ const relativePath = normalizedFilePath.substring(normalizedClientDir.length).replace(/^\//, "");
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
+
1811
1872
  // src/types.ts
1812
1873
  function normalizeAuthConfig(input) {
1813
1874
  if (!input)
@@ -1857,7 +1918,12 @@ async function generate(configPath) {
1857
1918
  console.log("\uD83D\uDD17 Building relationship graph...");
1858
1919
  const graph = buildGraph(model);
1859
1920
  const serverDir = cfg.outServer || "./generated/server";
1860
- const clientDir = cfg.outClient || "./generated/client";
1921
+ const originalClientDir = cfg.outClient || "./generated/client";
1922
+ const sameDirectory = serverDir === originalClientDir;
1923
+ let clientDir = originalClientDir;
1924
+ if (sameDirectory) {
1925
+ clientDir = join(originalClientDir, "sdk");
1926
+ }
1861
1927
  const normDateType = cfg.dateType === "string" ? "string" : "date";
1862
1928
  console.log("\uD83D\uDCC1 Creating directories...");
1863
1929
  await ensureDirs([
@@ -1914,11 +1980,18 @@ async function generate(configPath) {
1914
1980
  path: join(serverDir, "router.ts"),
1915
1981
  content: emitRouter(Object.values(model.tables), !!normalizedAuth?.strategy && normalizedAuth.strategy !== "none")
1916
1982
  });
1983
+ const clientFiles = files.filter((f) => {
1984
+ return f.path.includes(clientDir);
1985
+ });
1986
+ files.push({
1987
+ path: join(serverDir, "sdk-bundle.ts"),
1988
+ content: emitSdkBundle(clientFiles, clientDir)
1989
+ });
1917
1990
  console.log("✍️ Writing files...");
1918
1991
  await writeFiles(files);
1919
1992
  console.log(`✅ Generated ${files.length} files`);
1920
1993
  console.log(` Server: ${serverDir}`);
1921
- console.log(` Client: ${clientDir}`);
1994
+ console.log(` Client: ${sameDirectory ? clientDir + " (in sdk subdir due to same output dir)" : clientDir}`);
1922
1995
  }
1923
1996
  export {
1924
1997
  generate
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.1.2-alpha.4",
3
+ "version": "0.2.1-alpha.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/test-gen.ts",
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",