postgresdk 0.10.2 → 0.11.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
@@ -285,6 +285,41 @@ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
285
285
  const apiRouter = createRouter({ pg: pool });
286
286
  ```
287
287
 
288
+ ### Request-Level Middleware (onRequest Hook)
289
+
290
+ The `onRequest` hook executes before every endpoint operation, enabling:
291
+ - Setting PostgreSQL session variables for audit logging
292
+ - Configuring Row-Level Security (RLS) based on authenticated user
293
+ - Request-level logging or monitoring
294
+
295
+ ```typescript
296
+ import { createRouter } from "./api/server/router";
297
+
298
+ const apiRouter = createRouter({
299
+ pg,
300
+ onRequest: async (c, pg) => {
301
+ // Access Hono context - fully type-safe
302
+ const auth = c.get('auth');
303
+
304
+ // Set PostgreSQL session variable for audit triggers
305
+ if (auth?.kind === 'jwt' && auth.claims?.sub) {
306
+ await pg.query(`SET LOCAL app.user_id = '${auth.claims.sub}'`);
307
+ }
308
+
309
+ // Or configure RLS policies
310
+ if (auth?.tenant_id) {
311
+ await pg.query(`SET LOCAL app.tenant_id = '${auth.tenant_id}'`);
312
+ }
313
+ }
314
+ });
315
+ ```
316
+
317
+ The hook receives:
318
+ - `c` - Hono Context object with full type safety and IDE autocomplete
319
+ - `pg` - PostgreSQL client for setting session variables
320
+
321
+ **Note:** The router works with or without the `onRequest` hook - fully backward compatible.
322
+
288
323
  ## Server Integration
289
324
 
290
325
  postgresdk generates Hono-compatible routes:
package/dist/cli.js CHANGED
@@ -2634,6 +2634,7 @@ function emitHonoRoutes(table, _graph, opts) {
2634
2634
  * To make changes, modify your schema or configuration and regenerate.
2635
2635
  */
2636
2636
  import { Hono } from "hono";
2637
+ import type { Context } from "hono";
2637
2638
  import { z } from "zod";
2638
2639
  import { Insert${Type}Schema, Update${Type}Schema } from "../zod/${fileTableName}${ext}";
2639
2640
  import { loadIncludes } from "../include-loader${ext}";
@@ -2648,7 +2649,7 @@ const listSchema = z.object({
2648
2649
  orderBy: z.any().optional()
2649
2650
  });
2650
2651
 
2651
- export function register${Type}Routes(app: Hono, deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }) {
2652
+ 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> }) {
2652
2653
  const base = "/v1/${fileTableName}";
2653
2654
 
2654
2655
  // Create operation context
@@ -2668,12 +2669,16 @@ ${hasAuth ? `
2668
2669
  app.post(base, async (c) => {
2669
2670
  const body = await c.req.json().catch(() => ({}));
2670
2671
  const parsed = Insert${Type}Schema.safeParse(body);
2671
-
2672
+
2672
2673
  if (!parsed.success) {
2673
2674
  const issues = parsed.error.flatten();
2674
2675
  return c.json({ error: "Invalid body", issues }, 400);
2675
2676
  }
2676
-
2677
+
2678
+ if (deps.onRequest) {
2679
+ await deps.onRequest(c, deps.pg);
2680
+ }
2681
+
2677
2682
  const result = await coreOps.createRecord(ctx, parsed.data);
2678
2683
 
2679
2684
  if (result.error) {
@@ -2686,6 +2691,11 @@ ${hasAuth ? `
2686
2691
  // GET BY PK
2687
2692
  app.get(\`\${base}/${pkPath}\`, async (c) => {
2688
2693
  ${getPkParams}
2694
+
2695
+ if (deps.onRequest) {
2696
+ await deps.onRequest(c, deps.pg);
2697
+ }
2698
+
2689
2699
  const result = await coreOps.getByPk(ctx, pkValues);
2690
2700
 
2691
2701
  if (result.error) {
@@ -2698,12 +2708,16 @@ ${hasAuth ? `
2698
2708
  // LIST
2699
2709
  app.post(\`\${base}/list\`, async (c) => {
2700
2710
  const body = listSchema.safeParse(await c.req.json().catch(() => ({})));
2701
-
2711
+
2702
2712
  if (!body.success) {
2703
2713
  const issues = body.error.flatten();
2704
2714
  return c.json({ error: "Invalid body", issues }, 400);
2705
2715
  }
2706
-
2716
+
2717
+ if (deps.onRequest) {
2718
+ await deps.onRequest(c, deps.pg);
2719
+ }
2720
+
2707
2721
  const result = await coreOps.listRecords(ctx, body.data);
2708
2722
 
2709
2723
  if (result.error) {
@@ -2749,12 +2763,16 @@ ${hasAuth ? `
2749
2763
  ${getPkParams}
2750
2764
  const body = await c.req.json().catch(() => ({}));
2751
2765
  const parsed = Update${Type}Schema.safeParse(body);
2752
-
2766
+
2753
2767
  if (!parsed.success) {
2754
2768
  const issues = parsed.error.flatten();
2755
2769
  return c.json({ error: "Invalid body", issues }, 400);
2756
2770
  }
2757
-
2771
+
2772
+ if (deps.onRequest) {
2773
+ await deps.onRequest(c, deps.pg);
2774
+ }
2775
+
2758
2776
  const result = await coreOps.updateRecord(ctx, pkValues, parsed.data);
2759
2777
 
2760
2778
  if (result.error) {
@@ -2767,6 +2785,11 @@ ${hasAuth ? `
2767
2785
  // DELETE
2768
2786
  app.delete(\`\${base}/${pkPath}\`, async (c) => {
2769
2787
  ${getPkParams}
2788
+
2789
+ if (deps.onRequest) {
2790
+ await deps.onRequest(c, deps.pg);
2791
+ }
2792
+
2770
2793
  const result = await coreOps.deleteRecord(ctx, pkValues);
2771
2794
 
2772
2795
  if (result.error) {
@@ -2961,6 +2984,14 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
2961
2984
  for (const t of tables) {
2962
2985
  const Type = pascal(t.name);
2963
2986
  out += `export { ${Type}PkSchema, ${Type}ListParamsSchema, ${Type}OrderParamsSchema } from "./params/${t.name}${ext}";
2987
+ `;
2988
+ }
2989
+ out += `
2990
+ // Table types (Select, Insert, Update)
2991
+ `;
2992
+ for (const t of tables) {
2993
+ const Type = pascal(t.name);
2994
+ out += `export type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${t.name}${ext}";
2964
2995
  `;
2965
2996
  }
2966
2997
  return out;
@@ -3769,6 +3800,7 @@ function emitHonoRouter(tables, hasAuth, useJsExtensions) {
3769
3800
  * To make changes, modify your schema or configuration and regenerate.
3770
3801
  */
3771
3802
  import { Hono } from "hono";
3803
+ import type { Context } from "hono";
3772
3804
  import { SDK_MANIFEST } from "./sdk-bundle${ext}";
3773
3805
  import { getContract } from "./contract${ext}";
3774
3806
  ${imports}
@@ -3776,32 +3808,46 @@ ${hasAuth ? `export { authMiddleware } from "./auth${ext}";` : ""}
3776
3808
 
3777
3809
  /**
3778
3810
  * Creates a Hono router with all generated routes that can be mounted into your existing app.
3779
- *
3811
+ *
3780
3812
  * @example
3781
3813
  * import { Hono } from "hono";
3782
3814
  * import { createRouter } from "./generated/server/router";
3783
- *
3815
+ *
3784
3816
  * // Using pg driver (Node.js)
3785
3817
  * import { Client } from "pg";
3786
3818
  * const pg = new Client({ connectionString: process.env.DATABASE_URL });
3787
3819
  * await pg.connect();
3788
- *
3820
+ *
3789
3821
  * // OR using Neon driver (Edge-compatible)
3790
3822
  * import { Pool } from "@neondatabase/serverless";
3791
3823
  * const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
3792
3824
  * const pg = pool; // Pool already has the compatible query method
3793
- *
3825
+ *
3794
3826
  * // Mount all generated routes
3795
3827
  * const app = new Hono();
3796
3828
  * const apiRouter = createRouter({ pg });
3797
3829
  * app.route("/api", apiRouter);
3798
- *
3830
+ *
3799
3831
  * // Or mount directly at root
3800
3832
  * const router = createRouter({ pg });
3801
3833
  * app.route("/", router);
3834
+ *
3835
+ * // With onRequest hook for audit logging or session variables
3836
+ * const router = createRouter({
3837
+ * pg,
3838
+ * onRequest: async (c, pg) => {
3839
+ * const auth = c.get('auth'); // Type-safe! IDE autocomplete works
3840
+ * if (auth?.kind === 'jwt' && auth.claims?.sub) {
3841
+ * await pg.query(\`SET LOCAL app.user_id = '\${auth.claims.sub}'\`);
3842
+ * }
3843
+ * }
3844
+ * });
3802
3845
  */
3803
3846
  export function createRouter(
3804
- deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }
3847
+ deps: {
3848
+ pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> },
3849
+ onRequest?: (c: Context, pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }) => Promise<void>
3850
+ }
3805
3851
  ): Hono {
3806
3852
  const router = new Hono();
3807
3853
 
@@ -3860,22 +3906,36 @@ ${registrations}
3860
3906
 
3861
3907
  /**
3862
3908
  * Register all generated routes directly on an existing Hono app.
3863
- *
3909
+ *
3864
3910
  * @example
3865
3911
  * import { Hono } from "hono";
3866
3912
  * import { registerAllRoutes } from "./generated/server/router";
3867
- *
3913
+ *
3868
3914
  * const app = new Hono();
3869
- *
3915
+ *
3870
3916
  * // Setup database connection (see createRouter example for both pg and Neon options)
3871
3917
  * const pg = yourDatabaseClient;
3872
- *
3918
+ *
3873
3919
  * // Register all routes at once
3874
3920
  * registerAllRoutes(app, { pg });
3921
+ *
3922
+ * // With onRequest hook
3923
+ * registerAllRoutes(app, {
3924
+ * pg,
3925
+ * onRequest: async (c, pg) => {
3926
+ * const auth = c.get('auth'); // Type-safe!
3927
+ * if (auth?.kind === 'jwt' && auth.claims?.sub) {
3928
+ * await pg.query(\`SET LOCAL app.user_id = '\${auth.claims.sub}'\`);
3929
+ * }
3930
+ * }
3931
+ * });
3875
3932
  */
3876
3933
  export function registerAllRoutes(
3877
3934
  app: Hono,
3878
- deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }
3935
+ deps: {
3936
+ pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> },
3937
+ onRequest?: (c: Context, pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }) => Promise<void>
3938
+ }
3879
3939
  ) {
3880
3940
  ${registrations.replace(/router/g, "app")}
3881
3941
  }
package/dist/index.js CHANGED
@@ -1874,6 +1874,7 @@ function emitHonoRoutes(table, _graph, opts) {
1874
1874
  * To make changes, modify your schema or configuration and regenerate.
1875
1875
  */
1876
1876
  import { Hono } from "hono";
1877
+ import type { Context } from "hono";
1877
1878
  import { z } from "zod";
1878
1879
  import { Insert${Type}Schema, Update${Type}Schema } from "../zod/${fileTableName}${ext}";
1879
1880
  import { loadIncludes } from "../include-loader${ext}";
@@ -1888,7 +1889,7 @@ const listSchema = z.object({
1888
1889
  orderBy: z.any().optional()
1889
1890
  });
1890
1891
 
1891
- export function register${Type}Routes(app: Hono, deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }) {
1892
+ 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> }) {
1892
1893
  const base = "/v1/${fileTableName}";
1893
1894
 
1894
1895
  // Create operation context
@@ -1908,12 +1909,16 @@ ${hasAuth ? `
1908
1909
  app.post(base, async (c) => {
1909
1910
  const body = await c.req.json().catch(() => ({}));
1910
1911
  const parsed = Insert${Type}Schema.safeParse(body);
1911
-
1912
+
1912
1913
  if (!parsed.success) {
1913
1914
  const issues = parsed.error.flatten();
1914
1915
  return c.json({ error: "Invalid body", issues }, 400);
1915
1916
  }
1916
-
1917
+
1918
+ if (deps.onRequest) {
1919
+ await deps.onRequest(c, deps.pg);
1920
+ }
1921
+
1917
1922
  const result = await coreOps.createRecord(ctx, parsed.data);
1918
1923
 
1919
1924
  if (result.error) {
@@ -1926,6 +1931,11 @@ ${hasAuth ? `
1926
1931
  // GET BY PK
1927
1932
  app.get(\`\${base}/${pkPath}\`, async (c) => {
1928
1933
  ${getPkParams}
1934
+
1935
+ if (deps.onRequest) {
1936
+ await deps.onRequest(c, deps.pg);
1937
+ }
1938
+
1929
1939
  const result = await coreOps.getByPk(ctx, pkValues);
1930
1940
 
1931
1941
  if (result.error) {
@@ -1938,12 +1948,16 @@ ${hasAuth ? `
1938
1948
  // LIST
1939
1949
  app.post(\`\${base}/list\`, async (c) => {
1940
1950
  const body = listSchema.safeParse(await c.req.json().catch(() => ({})));
1941
-
1951
+
1942
1952
  if (!body.success) {
1943
1953
  const issues = body.error.flatten();
1944
1954
  return c.json({ error: "Invalid body", issues }, 400);
1945
1955
  }
1946
-
1956
+
1957
+ if (deps.onRequest) {
1958
+ await deps.onRequest(c, deps.pg);
1959
+ }
1960
+
1947
1961
  const result = await coreOps.listRecords(ctx, body.data);
1948
1962
 
1949
1963
  if (result.error) {
@@ -1989,12 +2003,16 @@ ${hasAuth ? `
1989
2003
  ${getPkParams}
1990
2004
  const body = await c.req.json().catch(() => ({}));
1991
2005
  const parsed = Update${Type}Schema.safeParse(body);
1992
-
2006
+
1993
2007
  if (!parsed.success) {
1994
2008
  const issues = parsed.error.flatten();
1995
2009
  return c.json({ error: "Invalid body", issues }, 400);
1996
2010
  }
1997
-
2011
+
2012
+ if (deps.onRequest) {
2013
+ await deps.onRequest(c, deps.pg);
2014
+ }
2015
+
1998
2016
  const result = await coreOps.updateRecord(ctx, pkValues, parsed.data);
1999
2017
 
2000
2018
  if (result.error) {
@@ -2007,6 +2025,11 @@ ${hasAuth ? `
2007
2025
  // DELETE
2008
2026
  app.delete(\`\${base}/${pkPath}\`, async (c) => {
2009
2027
  ${getPkParams}
2028
+
2029
+ if (deps.onRequest) {
2030
+ await deps.onRequest(c, deps.pg);
2031
+ }
2032
+
2010
2033
  const result = await coreOps.deleteRecord(ctx, pkValues);
2011
2034
 
2012
2035
  if (result.error) {
@@ -2201,6 +2224,14 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
2201
2224
  for (const t of tables) {
2202
2225
  const Type = pascal(t.name);
2203
2226
  out += `export { ${Type}PkSchema, ${Type}ListParamsSchema, ${Type}OrderParamsSchema } from "./params/${t.name}${ext}";
2227
+ `;
2228
+ }
2229
+ out += `
2230
+ // Table types (Select, Insert, Update)
2231
+ `;
2232
+ for (const t of tables) {
2233
+ const Type = pascal(t.name);
2234
+ out += `export type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${t.name}${ext}";
2204
2235
  `;
2205
2236
  }
2206
2237
  return out;
@@ -3009,6 +3040,7 @@ function emitHonoRouter(tables, hasAuth, useJsExtensions) {
3009
3040
  * To make changes, modify your schema or configuration and regenerate.
3010
3041
  */
3011
3042
  import { Hono } from "hono";
3043
+ import type { Context } from "hono";
3012
3044
  import { SDK_MANIFEST } from "./sdk-bundle${ext}";
3013
3045
  import { getContract } from "./contract${ext}";
3014
3046
  ${imports}
@@ -3016,32 +3048,46 @@ ${hasAuth ? `export { authMiddleware } from "./auth${ext}";` : ""}
3016
3048
 
3017
3049
  /**
3018
3050
  * Creates a Hono router with all generated routes that can be mounted into your existing app.
3019
- *
3051
+ *
3020
3052
  * @example
3021
3053
  * import { Hono } from "hono";
3022
3054
  * import { createRouter } from "./generated/server/router";
3023
- *
3055
+ *
3024
3056
  * // Using pg driver (Node.js)
3025
3057
  * import { Client } from "pg";
3026
3058
  * const pg = new Client({ connectionString: process.env.DATABASE_URL });
3027
3059
  * await pg.connect();
3028
- *
3060
+ *
3029
3061
  * // OR using Neon driver (Edge-compatible)
3030
3062
  * import { Pool } from "@neondatabase/serverless";
3031
3063
  * const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
3032
3064
  * const pg = pool; // Pool already has the compatible query method
3033
- *
3065
+ *
3034
3066
  * // Mount all generated routes
3035
3067
  * const app = new Hono();
3036
3068
  * const apiRouter = createRouter({ pg });
3037
3069
  * app.route("/api", apiRouter);
3038
- *
3070
+ *
3039
3071
  * // Or mount directly at root
3040
3072
  * const router = createRouter({ pg });
3041
3073
  * app.route("/", router);
3074
+ *
3075
+ * // With onRequest hook for audit logging or session variables
3076
+ * const router = createRouter({
3077
+ * pg,
3078
+ * onRequest: async (c, pg) => {
3079
+ * const auth = c.get('auth'); // Type-safe! IDE autocomplete works
3080
+ * if (auth?.kind === 'jwt' && auth.claims?.sub) {
3081
+ * await pg.query(\`SET LOCAL app.user_id = '\${auth.claims.sub}'\`);
3082
+ * }
3083
+ * }
3084
+ * });
3042
3085
  */
3043
3086
  export function createRouter(
3044
- deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }
3087
+ deps: {
3088
+ pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> },
3089
+ onRequest?: (c: Context, pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }) => Promise<void>
3090
+ }
3045
3091
  ): Hono {
3046
3092
  const router = new Hono();
3047
3093
 
@@ -3100,22 +3146,36 @@ ${registrations}
3100
3146
 
3101
3147
  /**
3102
3148
  * Register all generated routes directly on an existing Hono app.
3103
- *
3149
+ *
3104
3150
  * @example
3105
3151
  * import { Hono } from "hono";
3106
3152
  * import { registerAllRoutes } from "./generated/server/router";
3107
- *
3153
+ *
3108
3154
  * const app = new Hono();
3109
- *
3155
+ *
3110
3156
  * // Setup database connection (see createRouter example for both pg and Neon options)
3111
3157
  * const pg = yourDatabaseClient;
3112
- *
3158
+ *
3113
3159
  * // Register all routes at once
3114
3160
  * registerAllRoutes(app, { pg });
3161
+ *
3162
+ * // With onRequest hook
3163
+ * registerAllRoutes(app, {
3164
+ * pg,
3165
+ * onRequest: async (c, pg) => {
3166
+ * const auth = c.get('auth'); // Type-safe!
3167
+ * if (auth?.kind === 'jwt' && auth.claims?.sub) {
3168
+ * await pg.query(\`SET LOCAL app.user_id = '\${auth.claims.sub}'\`);
3169
+ * }
3170
+ * }
3171
+ * });
3115
3172
  */
3116
3173
  export function registerAllRoutes(
3117
3174
  app: Hono,
3118
- deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }
3175
+ deps: {
3176
+ pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> },
3177
+ onRequest?: (c: Context, pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }) => Promise<void>
3178
+ }
3119
3179
  ) {
3120
3180
  ${registrations.replace(/router/g, "app")}
3121
3181
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.10.2",
3
+ "version": "0.11.0",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {