postgresdk 0.10.4 → 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) {
@@ -3777,6 +3800,7 @@ function emitHonoRouter(tables, hasAuth, useJsExtensions) {
3777
3800
  * To make changes, modify your schema or configuration and regenerate.
3778
3801
  */
3779
3802
  import { Hono } from "hono";
3803
+ import type { Context } from "hono";
3780
3804
  import { SDK_MANIFEST } from "./sdk-bundle${ext}";
3781
3805
  import { getContract } from "./contract${ext}";
3782
3806
  ${imports}
@@ -3784,32 +3808,46 @@ ${hasAuth ? `export { authMiddleware } from "./auth${ext}";` : ""}
3784
3808
 
3785
3809
  /**
3786
3810
  * Creates a Hono router with all generated routes that can be mounted into your existing app.
3787
- *
3811
+ *
3788
3812
  * @example
3789
3813
  * import { Hono } from "hono";
3790
3814
  * import { createRouter } from "./generated/server/router";
3791
- *
3815
+ *
3792
3816
  * // Using pg driver (Node.js)
3793
3817
  * import { Client } from "pg";
3794
3818
  * const pg = new Client({ connectionString: process.env.DATABASE_URL });
3795
3819
  * await pg.connect();
3796
- *
3820
+ *
3797
3821
  * // OR using Neon driver (Edge-compatible)
3798
3822
  * import { Pool } from "@neondatabase/serverless";
3799
3823
  * const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
3800
3824
  * const pg = pool; // Pool already has the compatible query method
3801
- *
3825
+ *
3802
3826
  * // Mount all generated routes
3803
3827
  * const app = new Hono();
3804
3828
  * const apiRouter = createRouter({ pg });
3805
3829
  * app.route("/api", apiRouter);
3806
- *
3830
+ *
3807
3831
  * // Or mount directly at root
3808
3832
  * const router = createRouter({ pg });
3809
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
+ * });
3810
3845
  */
3811
3846
  export function createRouter(
3812
- 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
+ }
3813
3851
  ): Hono {
3814
3852
  const router = new Hono();
3815
3853
 
@@ -3868,22 +3906,36 @@ ${registrations}
3868
3906
 
3869
3907
  /**
3870
3908
  * Register all generated routes directly on an existing Hono app.
3871
- *
3909
+ *
3872
3910
  * @example
3873
3911
  * import { Hono } from "hono";
3874
3912
  * import { registerAllRoutes } from "./generated/server/router";
3875
- *
3913
+ *
3876
3914
  * const app = new Hono();
3877
- *
3915
+ *
3878
3916
  * // Setup database connection (see createRouter example for both pg and Neon options)
3879
3917
  * const pg = yourDatabaseClient;
3880
- *
3918
+ *
3881
3919
  * // Register all routes at once
3882
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
+ * });
3883
3932
  */
3884
3933
  export function registerAllRoutes(
3885
3934
  app: Hono,
3886
- 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
+ }
3887
3939
  ) {
3888
3940
  ${registrations.replace(/router/g, "app")}
3889
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) {
@@ -3017,6 +3040,7 @@ function emitHonoRouter(tables, hasAuth, useJsExtensions) {
3017
3040
  * To make changes, modify your schema or configuration and regenerate.
3018
3041
  */
3019
3042
  import { Hono } from "hono";
3043
+ import type { Context } from "hono";
3020
3044
  import { SDK_MANIFEST } from "./sdk-bundle${ext}";
3021
3045
  import { getContract } from "./contract${ext}";
3022
3046
  ${imports}
@@ -3024,32 +3048,46 @@ ${hasAuth ? `export { authMiddleware } from "./auth${ext}";` : ""}
3024
3048
 
3025
3049
  /**
3026
3050
  * Creates a Hono router with all generated routes that can be mounted into your existing app.
3027
- *
3051
+ *
3028
3052
  * @example
3029
3053
  * import { Hono } from "hono";
3030
3054
  * import { createRouter } from "./generated/server/router";
3031
- *
3055
+ *
3032
3056
  * // Using pg driver (Node.js)
3033
3057
  * import { Client } from "pg";
3034
3058
  * const pg = new Client({ connectionString: process.env.DATABASE_URL });
3035
3059
  * await pg.connect();
3036
- *
3060
+ *
3037
3061
  * // OR using Neon driver (Edge-compatible)
3038
3062
  * import { Pool } from "@neondatabase/serverless";
3039
3063
  * const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
3040
3064
  * const pg = pool; // Pool already has the compatible query method
3041
- *
3065
+ *
3042
3066
  * // Mount all generated routes
3043
3067
  * const app = new Hono();
3044
3068
  * const apiRouter = createRouter({ pg });
3045
3069
  * app.route("/api", apiRouter);
3046
- *
3070
+ *
3047
3071
  * // Or mount directly at root
3048
3072
  * const router = createRouter({ pg });
3049
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
+ * });
3050
3085
  */
3051
3086
  export function createRouter(
3052
- 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
+ }
3053
3091
  ): Hono {
3054
3092
  const router = new Hono();
3055
3093
 
@@ -3108,22 +3146,36 @@ ${registrations}
3108
3146
 
3109
3147
  /**
3110
3148
  * Register all generated routes directly on an existing Hono app.
3111
- *
3149
+ *
3112
3150
  * @example
3113
3151
  * import { Hono } from "hono";
3114
3152
  * import { registerAllRoutes } from "./generated/server/router";
3115
- *
3153
+ *
3116
3154
  * const app = new Hono();
3117
- *
3155
+ *
3118
3156
  * // Setup database connection (see createRouter example for both pg and Neon options)
3119
3157
  * const pg = yourDatabaseClient;
3120
- *
3158
+ *
3121
3159
  * // Register all routes at once
3122
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
+ * });
3123
3172
  */
3124
3173
  export function registerAllRoutes(
3125
3174
  app: Hono,
3126
- 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
+ }
3127
3179
  ) {
3128
3180
  ${registrations.replace(/router/g, "app")}
3129
3181
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.10.4",
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": {