postgresdk 0.13.1 → 0.14.1

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
@@ -4,6 +4,50 @@
4
4
 
5
5
  Generate a typed server/client SDK from your PostgreSQL database schema.
6
6
 
7
+ ## See It In Action
8
+
9
+ **Your database:**
10
+ ```sql
11
+ CREATE TABLE users (
12
+ id SERIAL PRIMARY KEY,
13
+ name TEXT NOT NULL,
14
+ email TEXT UNIQUE NOT NULL
15
+ );
16
+
17
+ CREATE TABLE posts (
18
+ id SERIAL PRIMARY KEY,
19
+ user_id INTEGER REFERENCES users(id),
20
+ title TEXT NOT NULL,
21
+ published_at TIMESTAMPTZ
22
+ );
23
+ ```
24
+
25
+ **What you get:**
26
+ ```typescript
27
+ // ✨ Fully typed client with autocomplete
28
+ const user = await sdk.users.create({
29
+ name: "Alice",
30
+ email: "alice@example.com"
31
+ });
32
+ // ^ TypeScript knows: user.id is number, user.name is string
33
+
34
+ // 🔗 Automatic relationship loading
35
+ const users = await sdk.users.list({
36
+ include: { posts: true }
37
+ });
38
+ // ^ users[0].posts is fully typed Post[]
39
+
40
+ // 🎯 Advanced filtering with type safety
41
+ const filtered = await sdk.users.list({
42
+ where: {
43
+ email: { $ilike: '%@company.com' },
44
+ posts: { published_at: { $isNot: null } }
45
+ }
46
+ });
47
+ ```
48
+
49
+ **All generated automatically. Zero boilerplate.**
50
+
7
51
  ## Features
8
52
 
9
53
  - 🚀 **Instant SDK Generation** - Point at your PostgreSQL database and get a complete SDK
@@ -20,14 +64,23 @@ Generate a typed server/client SDK from your PostgreSQL database schema.
20
64
  npm install -g postgresdk
21
65
  # or
22
66
  npx postgresdk generate
67
+
68
+ # With Bun
69
+ bun install -g postgresdk
70
+ # or
71
+ bunx postgresdk generate
23
72
  ```
24
73
 
74
+ > **Note:** Currently only generates **Hono** server code. See [Supported Frameworks](#supported-frameworks) for details.
75
+
25
76
  ## Quick Start
26
77
 
27
78
  1. Initialize your project:
28
79
 
29
80
  ```bash
30
81
  npx postgresdk init
82
+ # or with Bun
83
+ bunx postgresdk init
31
84
  ```
32
85
 
33
86
  This creates a `postgresdk.config.ts` file with all available options documented.
@@ -45,12 +98,13 @@ export default {
45
98
 
46
99
  ```bash
47
100
  postgresdk generate
101
+ # or with Bun
102
+ bunx postgresdk generate
48
103
  ```
49
104
 
50
- 4. Use the generated SDK:
105
+ 4. Set up your server:
51
106
 
52
107
  ```typescript
53
- // Server (Hono)
54
108
  import { Hono } from "hono";
55
109
  import { Client } from "pg";
56
110
  import { createRouter } from "./api/server/router";
@@ -61,8 +115,11 @@ await pg.connect();
61
115
 
62
116
  const api = createRouter({ pg });
63
117
  app.route("/", api);
118
+ ```
119
+
120
+ 5. Use the client SDK:
64
121
 
65
- // Client
122
+ ```typescript
66
123
  import { SDK } from "./api/client";
67
124
 
68
125
  const sdk = new SDK({ baseUrl: "http://localhost:3000" });
@@ -82,13 +139,12 @@ Create a `postgresdk.config.ts` file in your project root:
82
139
  export default {
83
140
  // Required
84
141
  connectionString: process.env.DATABASE_URL || "postgres://user:pass@localhost:5432/dbname",
85
-
142
+
86
143
  // Optional (with defaults)
87
144
  schema: "public", // Database schema to introspect
88
- outServer: "./api/server", // Server code output directory
89
- outClient: "./api/client", // Client SDK output directory
145
+ outDir: "./api", // Output directory (or { client: "./sdk", server: "./api" })
90
146
  softDeleteColumn: null, // Column name for soft deletes (e.g., "deleted_at")
91
- includeMethodsDepth: 2, // Max depth for nested includes
147
+ includeMethodsDepth: 2, // Max depth for nested includes
92
148
  dateType: "date", // "date" | "string" - How to handle timestamps
93
149
  serverFramework: "hono", // Currently only hono is supported
94
150
  useJsExtensions: false, // Add .js to imports (for Vercel Edge, Deno)
@@ -97,7 +153,11 @@ export default {
97
153
  auth: {
98
154
  apiKey: process.env.API_KEY, // Simple API key auth
99
155
  // OR
100
- jwt: process.env.JWT_SECRET, // Simple JWT auth
156
+ jwt: { // JWT with multi-service support
157
+ services: [
158
+ { issuer: "my-app", secret: process.env.JWT_SECRET }
159
+ ]
160
+ }
101
161
  },
102
162
 
103
163
  // Test generation (optional)
@@ -282,14 +342,35 @@ const sdk = new SDK({
282
342
  export default {
283
343
  connectionString: "...",
284
344
  auth: {
285
- jwt: process.env.JWT_SECRET
345
+ strategy: "jwt-hs256",
346
+ jwt: {
347
+ services: [
348
+ { issuer: "my-app", secret: process.env.JWT_SECRET }
349
+ ],
350
+ audience: "my-api" // Optional
351
+ }
352
+ }
353
+ };
354
+
355
+ // Multi-service example (each service has its own secret)
356
+ export default {
357
+ connectionString: "...",
358
+ auth: {
359
+ strategy: "jwt-hs256",
360
+ jwt: {
361
+ services: [
362
+ { issuer: "web-app", secret: process.env.WEB_APP_SECRET },
363
+ { issuer: "mobile-app", secret: process.env.MOBILE_SECRET },
364
+ ],
365
+ audience: "my-api"
366
+ }
286
367
  }
287
368
  };
288
369
 
289
370
  // Client SDK usage
290
371
  const sdk = new SDK({
291
372
  baseUrl: "http://localhost:3000",
292
- auth: { jwt: "eyJhbGciOiJIUzI1NiIs..." }
373
+ auth: { jwt: "eyJhbGciOiJIUzI1NiIs..." } // JWT must include 'iss' claim
293
374
  });
294
375
  ```
295
376
 
@@ -378,6 +459,42 @@ app.route("/", apiRouter);
378
459
  serve({ fetch: app.fetch, port: 3000 });
379
460
  ```
380
461
 
462
+ ## Deployment
463
+
464
+ ### Serverless (Vercel, Netlify, Cloudflare Workers)
465
+
466
+ Use `max: 1` - each serverless instance should hold one connection:
467
+
468
+ ```typescript
469
+ import { Pool } from "@neondatabase/serverless";
470
+
471
+ const pool = new Pool({
472
+ connectionString: process.env.DATABASE_URL,
473
+ max: 1 // One connection per serverless instance
474
+ });
475
+
476
+ const apiRouter = createRouter({ pg: pool });
477
+ ```
478
+
479
+ **Why `max: 1`?** Serverless functions are ephemeral and isolated. Each instance handles one request at a time, so connection pooling provides no benefit and wastes database connections.
480
+
481
+ ### Traditional Servers (Railway, Render, VPS)
482
+
483
+ Use connection pooling to reuse connections across requests:
484
+
485
+ ```typescript
486
+ import { Pool } from "@neondatabase/serverless";
487
+
488
+ const pool = new Pool({
489
+ connectionString: process.env.DATABASE_URL,
490
+ max: 10 // Reuse connections across requests
491
+ });
492
+
493
+ const apiRouter = createRouter({ pg: pool });
494
+ ```
495
+
496
+ **Why `max: 10`?** Long-running servers handle many concurrent requests. Pooling prevents opening/closing connections for every request, significantly improving performance.
497
+
381
498
  ## SDK Distribution
382
499
 
383
500
  Your generated SDK can be pulled by client applications:
@@ -420,6 +537,9 @@ Run tests with the included Docker setup:
420
537
  ```bash
421
538
  chmod +x api/tests/run-tests.sh
422
539
  ./api/tests/run-tests.sh
540
+
541
+ # Or with Bun's built-in test runner (if framework: "bun")
542
+ bun test
423
543
  ```
424
544
 
425
545
  ## CLI Commands
@@ -437,8 +557,14 @@ Commands:
437
557
  Options:
438
558
  -c, --config <path> Path to config file (default: postgresdk.config.ts)
439
559
 
560
+ Init flags:
561
+ --api Generate API-side config (for database introspection)
562
+ --sdk Generate SDK-side config (for consuming remote SDK)
563
+
440
564
  Examples:
441
- postgresdk init
565
+ postgresdk init # Interactive prompt
566
+ postgresdk init --api # API-side config
567
+ postgresdk init --sdk # SDK-side config
442
568
  postgresdk generate
443
569
  postgresdk generate -c custom.config.ts
444
570
  postgresdk pull --from=https://api.com --output=./src/sdk
@@ -446,10 +572,32 @@ Examples:
446
572
 
447
573
  ## Requirements
448
574
 
449
- - Node.js 18+
575
+ - Node.js 18+
450
576
  - PostgreSQL 12+
451
577
  - TypeScript project (for using generated code)
452
578
 
579
+ ## Supported Frameworks
580
+
581
+ **Currently, postgresdk only generates server code for Hono.**
582
+
583
+ While the configuration accepts `serverFramework: "hono" | "express" | "fastify"`, only Hono is implemented at this time. Attempting to generate code with `express` or `fastify` will result in an error.
584
+
585
+ ### Why Hono?
586
+
587
+ Hono was chosen as the initial framework because:
588
+ - **Edge-first design** - Works seamlessly in serverless and edge environments (Cloudflare Workers, Vercel Edge, Deno Deploy)
589
+ - **Minimal dependencies** - Lightweight with excellent performance
590
+ - **Modern patterns** - Web Standard APIs (Request/Response), TypeScript-first
591
+ - **Framework compatibility** - Works across Node.js, Bun, Deno, and edge runtimes
592
+
593
+ ### Future Framework Support
594
+
595
+ The codebase architecture is designed to support multiple frameworks. Adding Express or Fastify support would require:
596
+ - Implementing framework-specific route emitters (`emit-routes-express.ts`, etc.)
597
+ - Implementing framework-specific router creators (`emit-router-express.ts`, etc.)
598
+
599
+ Contributions to add additional framework support are welcome.
600
+
453
601
  ## License
454
602
 
455
603
  MIT
package/dist/cli.js CHANGED
@@ -1951,12 +1951,14 @@ function getDefaultComplexBlock(key) {
1951
1951
  // process.env.API_KEY_1,
1952
1952
  // process.env.API_KEY_2,
1953
1953
  // ],
1954
- //
1954
+ //
1955
1955
  // // For JWT (HS256) authentication
1956
1956
  // jwt: {
1957
- // sharedSecret: process.env.JWT_SECRET, // Secret for signing/verifying
1958
- // issuer: "my-app", // Optional: validate 'iss' claim
1959
- // audience: "my-users", // Optional: validate 'aud' claim
1957
+ // services: [ // Array of services that can authenticate
1958
+ // { issuer: "web-app", secret: process.env.WEB_APP_SECRET },
1959
+ // { issuer: "mobile-app", secret: process.env.MOBILE_SECRET },
1960
+ // ],
1961
+ // audience: "my-api", // Optional: validate 'aud' claim
1960
1962
  // }
1961
1963
  // },`;
1962
1964
  case "pull":
@@ -1983,6 +1985,8 @@ async function initCommand(args) {
1983
1985
  console.log(`\uD83D\uDE80 Initializing postgresdk configuration...
1984
1986
  `);
1985
1987
  const forceError = args.includes("--force-error");
1988
+ const isApiSide = args.includes("--api");
1989
+ const isSdkSide = args.includes("--sdk");
1986
1990
  const configPath = resolve(process.cwd(), "postgresdk.config.ts");
1987
1991
  if (existsSync(configPath)) {
1988
1992
  if (forceError) {
@@ -2102,21 +2106,65 @@ async function initCommand(args) {
2102
2106
  }
2103
2107
  return;
2104
2108
  }
2109
+ let projectType;
2110
+ if (isApiSide && isSdkSide) {
2111
+ console.error("❌ Error: Cannot use both --api and --sdk flags");
2112
+ process.exit(1);
2113
+ } else if (isApiSide) {
2114
+ projectType = "api";
2115
+ } else if (isSdkSide) {
2116
+ projectType = "sdk";
2117
+ } else {
2118
+ const response = await prompts({
2119
+ type: "select",
2120
+ name: "projectType",
2121
+ message: "What type of project is this?",
2122
+ choices: [
2123
+ {
2124
+ title: "API-side (generating SDK from database)",
2125
+ value: "api",
2126
+ description: "You have a PostgreSQL database and want to generate an SDK"
2127
+ },
2128
+ {
2129
+ title: "SDK-side (consuming a remote SDK)",
2130
+ value: "sdk",
2131
+ description: "You want to pull and use an SDK from a remote API"
2132
+ }
2133
+ ],
2134
+ initial: 0
2135
+ });
2136
+ projectType = response.projectType;
2137
+ if (!projectType) {
2138
+ console.log(`
2139
+ ✅ Cancelled. No changes made.`);
2140
+ process.exit(0);
2141
+ }
2142
+ }
2143
+ const template = projectType === "api" ? CONFIG_TEMPLATE_API : CONFIG_TEMPLATE_SDK;
2105
2144
  const envPath = resolve(process.cwd(), ".env");
2106
2145
  const hasEnv = existsSync(envPath);
2107
2146
  try {
2108
- writeFileSync(configPath, CONFIG_TEMPLATE, "utf-8");
2147
+ writeFileSync(configPath, template, "utf-8");
2109
2148
  console.log("✅ Created postgresdk.config.ts");
2110
2149
  console.log(`
2111
2150
  \uD83D\uDCDD Next steps:`);
2112
- console.log(" 1. Edit postgresdk.config.ts with your database connection");
2113
- if (!hasEnv) {
2114
- console.log(" 2. Consider creating a .env file for sensitive values:");
2115
- console.log(" DATABASE_URL=postgres://user:pass@localhost:5432/mydb");
2116
- console.log(" API_KEY=your-secret-key");
2117
- console.log(" JWT_SECRET=your-jwt-secret");
2118
- }
2119
- console.log(" 3. Run 'postgresdk generate' to create your SDK");
2151
+ if (projectType === "api") {
2152
+ console.log(" 1. Edit postgresdk.config.ts with your database connection");
2153
+ if (!hasEnv) {
2154
+ console.log(" 2. Consider creating a .env file for sensitive values:");
2155
+ console.log(" DATABASE_URL=postgres://user:pass@localhost:5432/mydb");
2156
+ console.log(" API_KEY=your-secret-key");
2157
+ console.log(" JWT_SECRET=your-jwt-secret");
2158
+ }
2159
+ console.log(" 3. Run 'postgresdk generate' to create your SDK");
2160
+ } else {
2161
+ console.log(" 1. Edit postgresdk.config.ts with your API URL in pull.from");
2162
+ if (!hasEnv) {
2163
+ console.log(" 2. Consider creating a .env file if you need authentication:");
2164
+ console.log(" API_TOKEN=your-api-token");
2165
+ }
2166
+ console.log(" 3. Run 'postgresdk pull' to fetch your SDK");
2167
+ }
2120
2168
  console.log(`
2121
2169
  \uD83D\uDCA1 Tip: The config file has detailed comments for all options.`);
2122
2170
  console.log(" Uncomment the options you want to customize.");
@@ -2125,8 +2173,8 @@ async function initCommand(args) {
2125
2173
  process.exit(1);
2126
2174
  }
2127
2175
  }
2128
- var CONFIG_TEMPLATE = `/**
2129
- * PostgreSDK Configuration
2176
+ var CONFIG_TEMPLATE_API = `/**
2177
+ * PostgreSDK Configuration (API-Side)
2130
2178
  *
2131
2179
  * This file configures how postgresdk generates your type-safe API and SDK
2132
2180
  * from your PostgreSQL database schema.
@@ -2137,9 +2185,7 @@ var CONFIG_TEMPLATE = `/**
2137
2185
  * 3. Start using your generated SDK!
2138
2186
  *
2139
2187
  * CLI COMMANDS:
2140
- * postgresdk init Initialize this config file
2141
2188
  * postgresdk generate Generate API and SDK from your database
2142
- * postgresdk pull Pull SDK from a remote API
2143
2189
  * postgresdk help Show help and examples
2144
2190
  *
2145
2191
  * Environment variables are automatically loaded from .env files.
@@ -2165,16 +2211,17 @@ export default {
2165
2211
  // schema: "public",
2166
2212
 
2167
2213
  /**
2168
- * Output directory for server-side code (routes, validators, etc.)
2169
- * Default: "./api/server"
2170
- */
2171
- // outServer: "./api/server",
2172
-
2173
- /**
2174
- * Output directory for client SDK
2175
- * Default: "./api/client"
2214
+ * Output directory for generated code
2215
+ *
2216
+ * Simple usage (same directory for both):
2217
+ * outDir: "./api"
2218
+ *
2219
+ * Separate directories for client and server:
2220
+ * outDir: { client: "./sdk", server: "./api" }
2221
+ *
2222
+ * Default: { client: "./api/client", server: "./api/server" }
2176
2223
  */
2177
- // outClient: "./api/client",
2224
+ // outDir: "./api",
2178
2225
 
2179
2226
  // ========== ADVANCED OPTIONS ==========
2180
2227
 
@@ -2254,23 +2301,42 @@ export default {
2254
2301
  //
2255
2302
  // // For JWT (HS256) authentication
2256
2303
  // jwt: {
2257
- // sharedSecret: process.env.JWT_SECRET, // Secret for signing/verifying
2258
- // issuer: "my-app", // Optional: validate 'iss' claim
2259
- // audience: "my-users", // Optional: validate 'aud' claim
2304
+ // services: [ // Array of services that can authenticate
2305
+ // { issuer: "web-app", secret: process.env.WEB_APP_SECRET },
2306
+ // { issuer: "mobile-app", secret: process.env.MOBILE_SECRET },
2307
+ // ],
2308
+ // audience: "my-api", // Optional: validate 'aud' claim
2260
2309
  // }
2261
2310
  // },
2311
+ };
2312
+ `, CONFIG_TEMPLATE_SDK = `/**
2313
+ * PostgreSDK Configuration (SDK-Side)
2314
+ *
2315
+ * This file configures how postgresdk pulls a generated SDK from a remote API.
2316
+ *
2317
+ * QUICK START:
2318
+ * 1. Update the 'pull.from' URL below
2319
+ * 2. Run: postgresdk pull
2320
+ * 3. Import and use the SDK!
2321
+ *
2322
+ * CLI COMMANDS:
2323
+ * postgresdk pull Pull SDK from a remote API
2324
+ * postgresdk help Show help and examples
2325
+ *
2326
+ * Environment variables are automatically loaded from .env files.
2327
+ */
2262
2328
 
2263
- // ========== SDK DISTRIBUTION (Pull Configuration) ==========
2329
+ export default {
2330
+ // ========== SDK PULL CONFIGURATION ==========
2264
2331
 
2265
2332
  /**
2266
2333
  * Configuration for pulling SDK from a remote API
2267
- * Used when running: postgresdk pull
2268
2334
  */
2269
- // pull: {
2270
- // from: "https://api.myapp.com", // API URL to pull SDK from
2271
- // output: "./src/sdk", // Local directory for pulled SDK
2272
- // token: process.env.API_TOKEN, // Optional authentication token
2273
- // },
2335
+ pull: {
2336
+ from: "https://api.myapp.com", // API URL to pull SDK from
2337
+ output: "./src/sdk", // Local directory for pulled SDK
2338
+ // token: process.env.API_TOKEN, // Optional authentication token
2339
+ },
2274
2340
  };
2275
2341
  `;
2276
2342
  var init_cli_init = __esm(() => {
@@ -2333,7 +2399,7 @@ Example config file:`);
2333
2399
  console.log(`\uD83D\uDCC1 Output directory: ${config.output}`);
2334
2400
  try {
2335
2401
  const headers = config.token ? { Authorization: `Bearer ${config.token}` } : {};
2336
- const manifestRes = await fetch(`${config.from}/sdk/manifest`, { headers });
2402
+ const manifestRes = await fetch(`${config.from}/_psdk/sdk/manifest`, { headers });
2337
2403
  if (!manifestRes.ok) {
2338
2404
  throw new Error(`Failed to fetch SDK manifest: ${manifestRes.status} ${manifestRes.statusText}`);
2339
2405
  }
@@ -2341,7 +2407,7 @@ Example config file:`);
2341
2407
  console.log(`\uD83D\uDCE6 SDK version: ${manifest.version}`);
2342
2408
  console.log(`\uD83D\uDCC5 Generated: ${manifest.generated}`);
2343
2409
  console.log(`\uD83D\uDCC4 Files: ${manifest.files.length}`);
2344
- const sdkRes = await fetch(`${config.from}/sdk/download`, { headers });
2410
+ const sdkRes = await fetch(`${config.from}/_psdk/sdk/download`, { headers });
2345
2411
  if (!sdkRes.ok) {
2346
2412
  throw new Error(`Failed to download SDK: ${sdkRes.status} ${sdkRes.statusText}`);
2347
2413
  }
@@ -2811,7 +2877,7 @@ const listSchema = z.object({
2811
2877
  * @param deps.onRequest - Optional hook that runs before each request (for audit logging, RLS, etc.)
2812
2878
  */
2813
2879
  export function register${Type}Routes(app: Hono, deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }, onRequest?: (c: Context, pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }) => Promise<void> }) {
2814
- const base = "/v1/${fileTableName}";
2880
+ const base = "${opts.apiPathPrefix}/${fileTableName}";
2815
2881
 
2816
2882
  // Create operation context
2817
2883
  const ctx: coreOps.OperationContext = {
@@ -3864,14 +3930,12 @@ function emitAuth(cfgAuth) {
3864
3930
  const strategy = cfgAuth?.strategy ?? "none";
3865
3931
  const apiKeyHeader = cfgAuth?.apiKeyHeader ?? "x-api-key";
3866
3932
  const apiKeys = cfgAuth?.apiKeys ?? [];
3867
- const jwtShared = cfgAuth?.jwt?.sharedSecret ?? "";
3868
- const jwtIssuer = cfgAuth?.jwt?.issuer ?? undefined;
3933
+ const jwtServices = cfgAuth?.jwt?.services ?? [];
3869
3934
  const jwtAudience = cfgAuth?.jwt?.audience ?? undefined;
3870
3935
  const STRATEGY = JSON.stringify(strategy);
3871
3936
  const API_KEY_HEADER = JSON.stringify(apiKeyHeader);
3872
3937
  const RAW_API_KEYS = JSON.stringify(apiKeys);
3873
- const JWT_SHARED_SECRET = JSON.stringify(jwtShared);
3874
- const JWT_ISSUER = jwtIssuer === undefined ? "undefined" : JSON.stringify(jwtIssuer);
3938
+ const JWT_SERVICES = JSON.stringify(jwtServices);
3875
3939
  const JWT_AUDIENCE = jwtAudience === undefined ? "undefined" : JSON.stringify(jwtAudience);
3876
3940
  return `/**
3877
3941
  * AUTO-GENERATED FILE - DO NOT EDIT
@@ -3889,8 +3953,7 @@ const STRATEGY = ${STRATEGY} as "none" | "api-key" | "jwt-hs256";
3889
3953
  const API_KEY_HEADER = ${API_KEY_HEADER} as string;
3890
3954
  const RAW_API_KEYS = ${RAW_API_KEYS} as readonly string[];
3891
3955
 
3892
- const JWT_SHARED_SECRET = ${JWT_SHARED_SECRET} as string;
3893
- const JWT_ISSUER = ${JWT_ISSUER} as string | undefined;
3956
+ const JWT_SERVICES = ${JWT_SERVICES} as ReadonlyArray<{ issuer: string; secret: string }>;
3894
3957
  const JWT_AUDIENCE = ${JWT_AUDIENCE} as string | undefined;
3895
3958
  // -------------------------------------
3896
3959
 
@@ -3931,7 +3994,12 @@ function resolveKeys(keys: readonly string[]): string[] {
3931
3994
  }
3932
3995
 
3933
3996
  const API_KEYS = resolveKeys(RAW_API_KEYS);
3934
- const HS256_SECRET = resolveValue(JWT_SHARED_SECRET);
3997
+
3998
+ // Resolve JWT service secrets from env vars if needed
3999
+ const RESOLVED_JWT_SERVICES = JWT_SERVICES.map(svc => ({
4000
+ issuer: svc.issuer,
4001
+ secret: resolveValue(svc.secret)
4002
+ }));
3935
4003
 
3936
4004
  // Augment Hono context for DX
3937
4005
  declare module "hono" {
@@ -3975,21 +4043,43 @@ export async function authMiddleware(c: Context, next: Next) {
3975
4043
  }
3976
4044
  const token = m[1];
3977
4045
 
3978
- if (!HS256_SECRET) {
3979
- log.error("JWT strategy configured but JWT_SHARED_SECRET is empty");
4046
+ if (!RESOLVED_JWT_SERVICES.length) {
4047
+ log.error("JWT strategy configured but no services defined");
3980
4048
  return c.json({ error: "Unauthorized" }, 401);
3981
4049
  }
3982
4050
 
3983
4051
  // Lazy import 'jose' so projects not using JWT don't need it at runtime.
3984
4052
  try {
3985
- const { jwtVerify } = await import("jose");
3986
- const key = new TextEncoder().encode(HS256_SECRET);
3987
- log.debug("Verifying JWT with secret:", HS256_SECRET ? "present" : "missing");
3988
- log.debug("Expected issuer:", JWT_ISSUER);
4053
+ const { decodeJwt, jwtVerify } = await import("jose");
4054
+
4055
+ // Decode without verification to extract issuer claim
4056
+ const unverifiedPayload = decodeJwt(token);
4057
+ const issuer = unverifiedPayload.iss;
4058
+
4059
+ if (!issuer) {
4060
+ log.error("JWT missing required 'iss' (issuer) claim");
4061
+ return c.json({ error: "Unauthorized" }, 401);
4062
+ }
4063
+
4064
+ // Find matching service by issuer
4065
+ const service = RESOLVED_JWT_SERVICES.find(s => s.issuer === issuer);
4066
+ if (!service) {
4067
+ log.error("Unknown JWT issuer:", issuer);
4068
+ return c.json({ error: "Unauthorized" }, 401);
4069
+ }
4070
+
4071
+ if (!service.secret) {
4072
+ log.error("JWT service configured but secret is empty for issuer:", issuer);
4073
+ return c.json({ error: "Unauthorized" }, 401);
4074
+ }
4075
+
4076
+ // Verify JWT using the service's secret
4077
+ const key = new TextEncoder().encode(service.secret);
4078
+ log.debug("Verifying JWT from issuer:", issuer);
3989
4079
  log.debug("Expected audience:", JWT_AUDIENCE);
3990
-
4080
+
3991
4081
  const { payload } = await jwtVerify(token, key, {
3992
- issuer: JWT_ISSUER || undefined,
4082
+ issuer: issuer,
3993
4083
  audience: JWT_AUDIENCE || undefined,
3994
4084
  });
3995
4085
 
@@ -3999,7 +4089,7 @@ export async function authMiddleware(c: Context, next: Next) {
3999
4089
  sub: typeof payload.sub === "string" ? payload.sub : undefined,
4000
4090
  claims: payload as any,
4001
4091
  });
4002
- log.debug("JWT verified successfully");
4092
+ log.debug("JWT verified successfully for issuer:", issuer);
4003
4093
  return next();
4004
4094
  } catch (verifyError: any) {
4005
4095
  log.error("JWT verification failed:", verifyError?.message || verifyError);
@@ -4064,10 +4154,22 @@ ${hasAuth ? `export { authMiddleware } from "./auth${ext}";` : ""}
4064
4154
  * const pg = new Client({ connectionString: process.env.DATABASE_URL });
4065
4155
  * await pg.connect();
4066
4156
  *
4067
- * // OR using Neon driver (Edge-compatible)
4157
+ * // OR using Neon driver
4068
4158
  * import { Pool } from "@neondatabase/serverless";
4069
- * const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
4070
- * const pg = pool; // Pool already has the compatible query method
4159
+ *
4160
+ * // For serverless (Vercel/Netlify) - one connection per instance
4161
+ * const pool = new Pool({
4162
+ * connectionString: process.env.DATABASE_URL!,
4163
+ * max: 1
4164
+ * });
4165
+ *
4166
+ * // For traditional servers - connection pooling
4167
+ * const pool = new Pool({
4168
+ * connectionString: process.env.DATABASE_URL!,
4169
+ * max: 10
4170
+ * });
4171
+ *
4172
+ * const pg = pool;
4071
4173
  *
4072
4174
  * // Mount all generated routes
4073
4175
  * const app = new Hono();
@@ -4099,21 +4201,21 @@ export function createRouter(
4099
4201
 
4100
4202
  // Register table routes
4101
4203
  ${registrations}
4102
-
4204
+
4103
4205
  // SDK distribution endpoints
4104
- router.get("/sdk/manifest", (c) => {
4206
+ router.get("/_psdk/sdk/manifest", (c) => {
4105
4207
  return c.json({
4106
4208
  version: SDK_MANIFEST.version,
4107
4209
  generated: SDK_MANIFEST.generated,
4108
4210
  files: Object.keys(SDK_MANIFEST.files)
4109
4211
  });
4110
4212
  });
4111
-
4112
- router.get("/sdk/download", (c) => {
4213
+
4214
+ router.get("/_psdk/sdk/download", (c) => {
4113
4215
  return c.json(SDK_MANIFEST);
4114
4216
  });
4115
-
4116
- router.get("/sdk/files/:path{.*}", (c) => {
4217
+
4218
+ router.get("/_psdk/sdk/files/:path{.*}", (c) => {
4117
4219
  const path = c.req.param("path");
4118
4220
  const content = SDK_MANIFEST.files[path as keyof typeof SDK_MANIFEST.files];
4119
4221
  if (!content) {
@@ -4123,25 +4225,25 @@ ${registrations}
4123
4225
  "Content-Type": "text/plain; charset=utf-8"
4124
4226
  });
4125
4227
  });
4126
-
4228
+
4127
4229
  // API Contract endpoints - describes the entire API
4128
- router.get("/api/contract", (c) => {
4230
+ router.get("/_psdk/contract", (c) => {
4129
4231
  const format = c.req.query("format") || "json";
4130
-
4232
+
4131
4233
  if (format === "markdown") {
4132
4234
  return c.text(getContract("markdown") as string, 200, {
4133
4235
  "Content-Type": "text/markdown; charset=utf-8"
4134
4236
  });
4135
4237
  }
4136
-
4238
+
4137
4239
  return c.json(getContract("json"));
4138
4240
  });
4139
-
4140
- router.get("/api/contract.json", (c) => {
4241
+
4242
+ router.get("/_psdk/contract.json", (c) => {
4141
4243
  return c.json(getContract("json"));
4142
4244
  });
4143
-
4144
- router.get("/api/contract.md", (c) => {
4245
+
4246
+ router.get("/_psdk/contract.md", (c) => {
4145
4247
  return c.text(getContract("markdown") as string, 200, {
4146
4248
  "Content-Type": "text/markdown; charset=utf-8"
4147
4249
  });
@@ -5397,17 +5499,10 @@ function normalizeAuthConfig(input) {
5397
5499
  };
5398
5500
  }
5399
5501
  if ("jwt" in input && input.jwt) {
5400
- if (typeof input.jwt === "string") {
5401
- return {
5402
- strategy: "jwt-hs256",
5403
- jwt: { sharedSecret: input.jwt }
5404
- };
5405
- } else {
5406
- return {
5407
- strategy: "jwt-hs256",
5408
- jwt: input.jwt
5409
- };
5410
- }
5502
+ return {
5503
+ strategy: "jwt-hs256",
5504
+ jwt: input.jwt
5505
+ };
5411
5506
  }
5412
5507
  return { strategy: "none" };
5413
5508
  }
@@ -5423,8 +5518,18 @@ async function generate(configPath) {
5423
5518
  const model = await introspect(cfg.connectionString, cfg.schema || "public");
5424
5519
  console.log("\uD83D\uDD17 Building relationship graph...");
5425
5520
  const graph = buildGraph(model);
5426
- const serverDir = cfg.outServer || "./api/server";
5427
- const originalClientDir = cfg.outClient || "./api/client";
5521
+ let serverDir;
5522
+ let originalClientDir;
5523
+ if (typeof cfg.outDir === "string") {
5524
+ serverDir = cfg.outDir;
5525
+ originalClientDir = cfg.outDir;
5526
+ } else if (cfg.outDir && typeof cfg.outDir === "object") {
5527
+ serverDir = cfg.outDir.server;
5528
+ originalClientDir = cfg.outDir.client;
5529
+ } else {
5530
+ serverDir = "./api/server";
5531
+ originalClientDir = "./api/client";
5532
+ }
5428
5533
  const sameDirectory = serverDir === originalClientDir;
5429
5534
  let clientDir = originalClientDir;
5430
5535
  if (sameDirectory) {
@@ -5495,7 +5600,8 @@ async function generate(configPath) {
5495
5600
  softDeleteColumn: cfg.softDeleteColumn || null,
5496
5601
  includeMethodsDepth: cfg.includeMethodsDepth || 2,
5497
5602
  authStrategy: normalizedAuth?.strategy,
5498
- useJsExtensions: cfg.useJsExtensions
5603
+ useJsExtensions: cfg.useJsExtensions,
5604
+ apiPathPrefix: cfg.apiPathPrefix || "/v1"
5499
5605
  });
5500
5606
  } else {
5501
5607
  throw new Error(`Framework "${serverFramework}" is not yet supported. Currently only "hono" is available.`);
@@ -5593,6 +5699,17 @@ async function generate(configPath) {
5593
5699
  console.log(` 2. Edit the script to configure your API server startup`);
5594
5700
  console.log(` 3. Run tests: ${testDir}/run-tests.sh`);
5595
5701
  }
5702
+ console.log(`
5703
+ \uD83D\uDCDA Usage:`);
5704
+ console.log(` Server (${serverFramework}):`);
5705
+ console.log(` import { createRouter } from "./${relative(process.cwd(), serverDir)}/router";`);
5706
+ console.log(` const api = createRouter({ pg });`);
5707
+ console.log(` app.route("/", api);`);
5708
+ console.log(`
5709
+ Client:`);
5710
+ console.log(` import { SDK } from "./${relative(process.cwd(), clientDir)}";`);
5711
+ console.log(` const sdk = new SDK({ baseUrl: "http://localhost:3000" });`);
5712
+ console.log(` const users = await sdk.users.list();`);
5596
5713
  }
5597
5714
 
5598
5715
  // src/cli.ts
@@ -8,4 +8,5 @@ export declare function emitHonoRoutes(table: Table, _graph: Graph, opts: {
8
8
  includeMethodsDepth: number;
9
9
  authStrategy?: string;
10
10
  useJsExtensions?: boolean;
11
+ apiPathPrefix: string;
11
12
  }): string;
package/dist/index.js CHANGED
@@ -2051,7 +2051,7 @@ const listSchema = z.object({
2051
2051
  * @param deps.onRequest - Optional hook that runs before each request (for audit logging, RLS, etc.)
2052
2052
  */
2053
2053
  export function register${Type}Routes(app: Hono, deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }, onRequest?: (c: Context, pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }) => Promise<void> }) {
2054
- const base = "/v1/${fileTableName}";
2054
+ const base = "${opts.apiPathPrefix}/${fileTableName}";
2055
2055
 
2056
2056
  // Create operation context
2057
2057
  const ctx: coreOps.OperationContext = {
@@ -3104,14 +3104,12 @@ function emitAuth(cfgAuth) {
3104
3104
  const strategy = cfgAuth?.strategy ?? "none";
3105
3105
  const apiKeyHeader = cfgAuth?.apiKeyHeader ?? "x-api-key";
3106
3106
  const apiKeys = cfgAuth?.apiKeys ?? [];
3107
- const jwtShared = cfgAuth?.jwt?.sharedSecret ?? "";
3108
- const jwtIssuer = cfgAuth?.jwt?.issuer ?? undefined;
3107
+ const jwtServices = cfgAuth?.jwt?.services ?? [];
3109
3108
  const jwtAudience = cfgAuth?.jwt?.audience ?? undefined;
3110
3109
  const STRATEGY = JSON.stringify(strategy);
3111
3110
  const API_KEY_HEADER = JSON.stringify(apiKeyHeader);
3112
3111
  const RAW_API_KEYS = JSON.stringify(apiKeys);
3113
- const JWT_SHARED_SECRET = JSON.stringify(jwtShared);
3114
- const JWT_ISSUER = jwtIssuer === undefined ? "undefined" : JSON.stringify(jwtIssuer);
3112
+ const JWT_SERVICES = JSON.stringify(jwtServices);
3115
3113
  const JWT_AUDIENCE = jwtAudience === undefined ? "undefined" : JSON.stringify(jwtAudience);
3116
3114
  return `/**
3117
3115
  * AUTO-GENERATED FILE - DO NOT EDIT
@@ -3129,8 +3127,7 @@ const STRATEGY = ${STRATEGY} as "none" | "api-key" | "jwt-hs256";
3129
3127
  const API_KEY_HEADER = ${API_KEY_HEADER} as string;
3130
3128
  const RAW_API_KEYS = ${RAW_API_KEYS} as readonly string[];
3131
3129
 
3132
- const JWT_SHARED_SECRET = ${JWT_SHARED_SECRET} as string;
3133
- const JWT_ISSUER = ${JWT_ISSUER} as string | undefined;
3130
+ const JWT_SERVICES = ${JWT_SERVICES} as ReadonlyArray<{ issuer: string; secret: string }>;
3134
3131
  const JWT_AUDIENCE = ${JWT_AUDIENCE} as string | undefined;
3135
3132
  // -------------------------------------
3136
3133
 
@@ -3171,7 +3168,12 @@ function resolveKeys(keys: readonly string[]): string[] {
3171
3168
  }
3172
3169
 
3173
3170
  const API_KEYS = resolveKeys(RAW_API_KEYS);
3174
- const HS256_SECRET = resolveValue(JWT_SHARED_SECRET);
3171
+
3172
+ // Resolve JWT service secrets from env vars if needed
3173
+ const RESOLVED_JWT_SERVICES = JWT_SERVICES.map(svc => ({
3174
+ issuer: svc.issuer,
3175
+ secret: resolveValue(svc.secret)
3176
+ }));
3175
3177
 
3176
3178
  // Augment Hono context for DX
3177
3179
  declare module "hono" {
@@ -3215,21 +3217,43 @@ export async function authMiddleware(c: Context, next: Next) {
3215
3217
  }
3216
3218
  const token = m[1];
3217
3219
 
3218
- if (!HS256_SECRET) {
3219
- log.error("JWT strategy configured but JWT_SHARED_SECRET is empty");
3220
+ if (!RESOLVED_JWT_SERVICES.length) {
3221
+ log.error("JWT strategy configured but no services defined");
3220
3222
  return c.json({ error: "Unauthorized" }, 401);
3221
3223
  }
3222
3224
 
3223
3225
  // Lazy import 'jose' so projects not using JWT don't need it at runtime.
3224
3226
  try {
3225
- const { jwtVerify } = await import("jose");
3226
- const key = new TextEncoder().encode(HS256_SECRET);
3227
- log.debug("Verifying JWT with secret:", HS256_SECRET ? "present" : "missing");
3228
- log.debug("Expected issuer:", JWT_ISSUER);
3227
+ const { decodeJwt, jwtVerify } = await import("jose");
3228
+
3229
+ // Decode without verification to extract issuer claim
3230
+ const unverifiedPayload = decodeJwt(token);
3231
+ const issuer = unverifiedPayload.iss;
3232
+
3233
+ if (!issuer) {
3234
+ log.error("JWT missing required 'iss' (issuer) claim");
3235
+ return c.json({ error: "Unauthorized" }, 401);
3236
+ }
3237
+
3238
+ // Find matching service by issuer
3239
+ const service = RESOLVED_JWT_SERVICES.find(s => s.issuer === issuer);
3240
+ if (!service) {
3241
+ log.error("Unknown JWT issuer:", issuer);
3242
+ return c.json({ error: "Unauthorized" }, 401);
3243
+ }
3244
+
3245
+ if (!service.secret) {
3246
+ log.error("JWT service configured but secret is empty for issuer:", issuer);
3247
+ return c.json({ error: "Unauthorized" }, 401);
3248
+ }
3249
+
3250
+ // Verify JWT using the service's secret
3251
+ const key = new TextEncoder().encode(service.secret);
3252
+ log.debug("Verifying JWT from issuer:", issuer);
3229
3253
  log.debug("Expected audience:", JWT_AUDIENCE);
3230
-
3254
+
3231
3255
  const { payload } = await jwtVerify(token, key, {
3232
- issuer: JWT_ISSUER || undefined,
3256
+ issuer: issuer,
3233
3257
  audience: JWT_AUDIENCE || undefined,
3234
3258
  });
3235
3259
 
@@ -3239,7 +3263,7 @@ export async function authMiddleware(c: Context, next: Next) {
3239
3263
  sub: typeof payload.sub === "string" ? payload.sub : undefined,
3240
3264
  claims: payload as any,
3241
3265
  });
3242
- log.debug("JWT verified successfully");
3266
+ log.debug("JWT verified successfully for issuer:", issuer);
3243
3267
  return next();
3244
3268
  } catch (verifyError: any) {
3245
3269
  log.error("JWT verification failed:", verifyError?.message || verifyError);
@@ -3304,10 +3328,22 @@ ${hasAuth ? `export { authMiddleware } from "./auth${ext}";` : ""}
3304
3328
  * const pg = new Client({ connectionString: process.env.DATABASE_URL });
3305
3329
  * await pg.connect();
3306
3330
  *
3307
- * // OR using Neon driver (Edge-compatible)
3331
+ * // OR using Neon driver
3308
3332
  * import { Pool } from "@neondatabase/serverless";
3309
- * const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
3310
- * const pg = pool; // Pool already has the compatible query method
3333
+ *
3334
+ * // For serverless (Vercel/Netlify) - one connection per instance
3335
+ * const pool = new Pool({
3336
+ * connectionString: process.env.DATABASE_URL!,
3337
+ * max: 1
3338
+ * });
3339
+ *
3340
+ * // For traditional servers - connection pooling
3341
+ * const pool = new Pool({
3342
+ * connectionString: process.env.DATABASE_URL!,
3343
+ * max: 10
3344
+ * });
3345
+ *
3346
+ * const pg = pool;
3311
3347
  *
3312
3348
  * // Mount all generated routes
3313
3349
  * const app = new Hono();
@@ -3339,21 +3375,21 @@ export function createRouter(
3339
3375
 
3340
3376
  // Register table routes
3341
3377
  ${registrations}
3342
-
3378
+
3343
3379
  // SDK distribution endpoints
3344
- router.get("/sdk/manifest", (c) => {
3380
+ router.get("/_psdk/sdk/manifest", (c) => {
3345
3381
  return c.json({
3346
3382
  version: SDK_MANIFEST.version,
3347
3383
  generated: SDK_MANIFEST.generated,
3348
3384
  files: Object.keys(SDK_MANIFEST.files)
3349
3385
  });
3350
3386
  });
3351
-
3352
- router.get("/sdk/download", (c) => {
3387
+
3388
+ router.get("/_psdk/sdk/download", (c) => {
3353
3389
  return c.json(SDK_MANIFEST);
3354
3390
  });
3355
-
3356
- router.get("/sdk/files/:path{.*}", (c) => {
3391
+
3392
+ router.get("/_psdk/sdk/files/:path{.*}", (c) => {
3357
3393
  const path = c.req.param("path");
3358
3394
  const content = SDK_MANIFEST.files[path as keyof typeof SDK_MANIFEST.files];
3359
3395
  if (!content) {
@@ -3363,25 +3399,25 @@ ${registrations}
3363
3399
  "Content-Type": "text/plain; charset=utf-8"
3364
3400
  });
3365
3401
  });
3366
-
3402
+
3367
3403
  // API Contract endpoints - describes the entire API
3368
- router.get("/api/contract", (c) => {
3404
+ router.get("/_psdk/contract", (c) => {
3369
3405
  const format = c.req.query("format") || "json";
3370
-
3406
+
3371
3407
  if (format === "markdown") {
3372
3408
  return c.text(getContract("markdown") as string, 200, {
3373
3409
  "Content-Type": "text/markdown; charset=utf-8"
3374
3410
  });
3375
3411
  }
3376
-
3412
+
3377
3413
  return c.json(getContract("json"));
3378
3414
  });
3379
-
3380
- router.get("/api/contract.json", (c) => {
3415
+
3416
+ router.get("/_psdk/contract.json", (c) => {
3381
3417
  return c.json(getContract("json"));
3382
3418
  });
3383
-
3384
- router.get("/api/contract.md", (c) => {
3419
+
3420
+ router.get("/_psdk/contract.md", (c) => {
3385
3421
  return c.text(getContract("markdown") as string, 200, {
3386
3422
  "Content-Type": "text/markdown; charset=utf-8"
3387
3423
  });
@@ -4637,17 +4673,10 @@ function normalizeAuthConfig(input) {
4637
4673
  };
4638
4674
  }
4639
4675
  if ("jwt" in input && input.jwt) {
4640
- if (typeof input.jwt === "string") {
4641
- return {
4642
- strategy: "jwt-hs256",
4643
- jwt: { sharedSecret: input.jwt }
4644
- };
4645
- } else {
4646
- return {
4647
- strategy: "jwt-hs256",
4648
- jwt: input.jwt
4649
- };
4650
- }
4676
+ return {
4677
+ strategy: "jwt-hs256",
4678
+ jwt: input.jwt
4679
+ };
4651
4680
  }
4652
4681
  return { strategy: "none" };
4653
4682
  }
@@ -4663,8 +4692,18 @@ async function generate(configPath) {
4663
4692
  const model = await introspect(cfg.connectionString, cfg.schema || "public");
4664
4693
  console.log("\uD83D\uDD17 Building relationship graph...");
4665
4694
  const graph = buildGraph(model);
4666
- const serverDir = cfg.outServer || "./api/server";
4667
- const originalClientDir = cfg.outClient || "./api/client";
4695
+ let serverDir;
4696
+ let originalClientDir;
4697
+ if (typeof cfg.outDir === "string") {
4698
+ serverDir = cfg.outDir;
4699
+ originalClientDir = cfg.outDir;
4700
+ } else if (cfg.outDir && typeof cfg.outDir === "object") {
4701
+ serverDir = cfg.outDir.server;
4702
+ originalClientDir = cfg.outDir.client;
4703
+ } else {
4704
+ serverDir = "./api/server";
4705
+ originalClientDir = "./api/client";
4706
+ }
4668
4707
  const sameDirectory = serverDir === originalClientDir;
4669
4708
  let clientDir = originalClientDir;
4670
4709
  if (sameDirectory) {
@@ -4735,7 +4774,8 @@ async function generate(configPath) {
4735
4774
  softDeleteColumn: cfg.softDeleteColumn || null,
4736
4775
  includeMethodsDepth: cfg.includeMethodsDepth || 2,
4737
4776
  authStrategy: normalizedAuth?.strategy,
4738
- useJsExtensions: cfg.useJsExtensions
4777
+ useJsExtensions: cfg.useJsExtensions,
4778
+ apiPathPrefix: cfg.apiPathPrefix || "/v1"
4739
4779
  });
4740
4780
  } else {
4741
4781
  throw new Error(`Framework "${serverFramework}" is not yet supported. Currently only "hono" is available.`);
@@ -4833,6 +4873,17 @@ async function generate(configPath) {
4833
4873
  console.log(` 2. Edit the script to configure your API server startup`);
4834
4874
  console.log(` 3. Run tests: ${testDir}/run-tests.sh`);
4835
4875
  }
4876
+ console.log(`
4877
+ \uD83D\uDCDA Usage:`);
4878
+ console.log(` Server (${serverFramework}):`);
4879
+ console.log(` import { createRouter } from "./${relative(process.cwd(), serverDir)}/router";`);
4880
+ console.log(` const api = createRouter({ pg });`);
4881
+ console.log(` app.route("/", api);`);
4882
+ console.log(`
4883
+ Client:`);
4884
+ console.log(` import { SDK } from "./${relative(process.cwd(), clientDir)}";`);
4885
+ console.log(` const sdk = new SDK({ baseUrl: "http://localhost:3000" });`);
4886
+ console.log(` const users = await sdk.users.list();`);
4836
4887
  }
4837
4888
  export {
4838
4889
  generate
package/dist/types.d.ts CHANGED
@@ -3,8 +3,10 @@ export interface AuthConfig {
3
3
  apiKeyHeader?: string;
4
4
  apiKeys?: string[];
5
5
  jwt?: {
6
- sharedSecret?: string;
7
- issuer?: string;
6
+ services: Array<{
7
+ issuer: string;
8
+ secret: string;
9
+ }>;
8
10
  audience?: string;
9
11
  };
10
12
  }
@@ -12,22 +14,20 @@ export type AuthConfigInput = AuthConfig | {
12
14
  apiKey?: string;
13
15
  apiKeys?: string[];
14
16
  apiKeyHeader?: string;
15
- jwt?: string | {
16
- sharedSecret?: string;
17
- issuer?: string;
18
- audience?: string;
19
- };
20
17
  };
21
18
  export interface Config {
22
19
  connectionString: string;
23
20
  schema?: string;
24
- outServer?: string;
25
- outClient?: string;
21
+ outDir?: string | {
22
+ client: string;
23
+ server: string;
24
+ };
26
25
  softDeleteColumn?: string | null;
27
26
  dateType?: "date" | "string";
28
27
  includeMethodsDepth?: number;
29
28
  skipJunctionTables?: boolean;
30
29
  serverFramework?: "hono" | "express" | "fastify";
30
+ apiPathPrefix?: string;
31
31
  auth?: AuthConfigInput;
32
32
  pull?: PullConfig;
33
33
  useJsExtensions?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.13.1",
3
+ "version": "0.14.1",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,14 +45,14 @@
45
45
  "zod": "^4.0.15"
46
46
  },
47
47
  "devDependencies": {
48
+ "@hono/node-server": "^1.19.7",
48
49
  "@types/bun": "^1.2.20",
49
50
  "@types/node": "^20.0.0",
50
51
  "@types/pg": "^8.15.5",
51
52
  "drizzle-kit": "^0.31.4",
52
53
  "drizzle-orm": "^0.44.4",
53
54
  "jose": "^6.0.12",
54
- "typescript": "^5.5.0",
55
- "vitest": "^3.2.4"
55
+ "typescript": "^5.5.0"
56
56
  },
57
57
  "author": "Ben Honda <ben@theadpharm.com>",
58
58
  "license": "MIT",