@ingram-tech/nk-db 0.2.1 → 0.4.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
@@ -39,7 +39,7 @@ import * as schema from "./schema";
39
39
 
40
40
  export const pool = createPool(); // TLS-aware; local socket → max:1
41
41
  export const db = createDb(pool, schema); // Drizzle — the default query path
42
- export const { query, one, maybeOne, execute, withTx } = createQueries(pool);
42
+ export const { query, one, maybeOne, execute, withTx, withRls } = createQueries(pool);
43
43
  export { schema };
44
44
  ```
45
45
 
@@ -55,6 +55,55 @@ same pool: `betterAuth({ database: pool, … })`.
55
55
  `timestamptz` as ISO strings (on the golden path, prefer Drizzle's
56
56
  `timestamp(..., { mode: "string" })` per column).
57
57
 
58
+ ## Keeping RLS on a direct connection (`withRls` / `withRlsTransaction`)
59
+
60
+ A direct `pg`/Drizzle connection has no PostgREST, so the `SET ROLE authenticated`
61
+ + `request.jwt.claims` setup that made `auth.uid()` policies fire is gone — a
62
+ plain query runs as the connection's role with no claims. These helpers reproduce
63
+ that setup **per transaction**, so your **existing RLS policies keep working
64
+ unchanged**, whether you're still on Supabase Postgres or already on DO. It's
65
+ pure Postgres and behaves identically on both.
66
+
67
+ ```ts
68
+ import { withRlsTransaction } from "@ingram-tech/nk-db";
69
+ import { auth } from "@/lib/auth"; // your Better Auth instance
70
+ import { db } from "@/lib/db";
71
+
72
+ const session = await auth.api.getSession({ headers });
73
+ // scoped: sets request.jwt.claims + SET LOCAL ROLE authenticated, then runs fn
74
+ const notes = await withRlsTransaction(db, { sub: session.user.id }, (tx) =>
75
+ tx.select().from(schema.notes), // returns only this user's rows
76
+ );
77
+ ```
78
+
79
+ The claims come **straight from the Better Auth session** (`sub` = `user.id`) — no
80
+ JWT minting, no JWKS issuer, no `supabase.auth`. The raw helpers expose the same
81
+ thing as `withRls` (sibling of `withTx`):
82
+
83
+ ```ts
84
+ const { withRls } = createQueries(pool);
85
+ const rows = await withRls({ sub: userId }, (tx) =>
86
+ tx.query<Note>("select * from notes"),
87
+ );
88
+ ```
89
+
90
+ Two requirements **you** own (they can't be enforced from the library):
91
+
92
+ - **Connect as a role that doesn't bypass RLS** for user-facing rows — not the
93
+ table owner, not a `BYPASSRLS` superuser. After `SET ROLE authenticated`, RLS
94
+ applies even if the underlying connection is `postgres` (exactly what PostgREST
95
+ relied on). Service-role/admin paths keep using plain `db` / `query` and bypass
96
+ RLS as before.
97
+ - **The connecting role must be allowed to `SET ROLE`** to the target
98
+ (Supabase's `authenticator`/`postgres` already can; on DO, `GRANT app_user TO …`).
99
+
100
+ Override the role / claims GUC when your DB role name differs from the JWT claim:
101
+ `withRlsTransaction(db, { sub }, fn, { role: "app_user" })`. Both helpers set the
102
+ GUCs **transaction-locally** (`is_local = true`), so they reset at
103
+ commit/rollback and never leak across pooled connections. See
104
+ [`docs/db-package.md` §RLS](https://github.com/ingram-technologies/nextkit/blob/main/docs/db-package.md)
105
+ and [`docs/better-auth-migration.md`](https://github.com/ingram-technologies/nextkit/blob/main/docs/better-auth-migration.md).
106
+
58
107
  ## PGlite dev & test (`@ingram-tech/nk-db/pglite`)
59
108
 
60
109
  `nk dev` runs the `nk-pglite-dev` bin automatically when this package is
package/dist/drizzle.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type NodePgDatabase } from "drizzle-orm/node-postgres";
2
2
  import type { Pool } from "pg";
3
+ import { type RlsClaims, type RlsOptions } from "./rls.js";
3
4
  /**
4
5
  * Build the Drizzle instance on the shared pool. This is the default query path
5
6
  * for app tables — schema-first, with `drizzle-kit` generating the migrations
@@ -11,4 +12,21 @@ import type { Pool } from "pg";
11
12
  * export const db = createDb(pool, schema);
12
13
  */
13
14
  export declare const createDb: <TSchema extends Record<string, unknown>>(pool: Pool, schema: TSchema) => NodePgDatabase<TSchema>;
15
+ /** The transaction handle Drizzle hands its `db.transaction(fn)` callback. */
16
+ type DrizzleTx<TSchema extends Record<string, unknown>> = Parameters<Parameters<NodePgDatabase<TSchema>["transaction"]>[0]>[0];
17
+ /**
18
+ * Run `fn` inside a Drizzle transaction **scoped to a user**, so RLS policies
19
+ * apply on a direct connection (no PostgREST). It sets `request.jwt.claims` +
20
+ * `SET LOCAL ROLE` (default `authenticated`) as the first statement, so existing
21
+ * `auth.uid()` policies fire unchanged; the `tx` passed to `fn` is a normal
22
+ * Drizzle transaction (full query builder + `tx.execute`). Use for user-facing
23
+ * reads/writes; use plain `db` for service-role paths. The connection role must
24
+ * not bypass RLS for the rows touched — see `rls.ts`.
25
+ *
26
+ * const notes = await withRlsTransaction(db, { sub: user.id }, (tx) =>
27
+ * tx.select().from(schema.notes),
28
+ * );
29
+ */
30
+ export declare const withRlsTransaction: <TSchema extends Record<string, unknown>, T>(db: NodePgDatabase<TSchema>, claims: RlsClaims, fn: (tx: DrizzleTx<TSchema>) => Promise<T>, options?: RlsOptions) => Promise<T>;
31
+ export {};
14
32
  //# sourceMappingURL=drizzle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../src/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,MAAM,IAAI,EACV,QAAQ,OAAO,KACb,cAAc,CAAC,OAAO,CAA8B,CAAC"}
1
+ {"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../src/drizzle.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAoB,MAAM,UAAU,CAAC;AAE7E;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,MAAM,IAAI,EACV,QAAQ,OAAO,KACb,cAAc,CAAC,OAAO,CAA8B,CAAC;AAExD,8EAA8E;AAC9E,KAAK,SAAS,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,UAAU,CACnE,UAAU,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CACrD,CAAC,CAAC,CAAC,CAAC;AAEL;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,EAC5E,IAAI,cAAc,CAAC,OAAO,CAAC,EAC3B,QAAQ,SAAS,EACjB,IAAI,CAAC,EAAE,EAAE,SAAS,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAC1C,UAAS,UAAe,KACtB,OAAO,CAAC,CAAC,CAOT,CAAC"}
package/dist/drizzle.js CHANGED
@@ -1,4 +1,6 @@
1
+ import { sql } from "drizzle-orm";
1
2
  import { drizzle } from "drizzle-orm/node-postgres";
3
+ import { resolveRlsConfig } from "./rls.js";
2
4
  /**
3
5
  * Build the Drizzle instance on the shared pool. This is the default query path
4
6
  * for app tables — schema-first, with `drizzle-kit` generating the migrations
@@ -10,4 +12,22 @@ import { drizzle } from "drizzle-orm/node-postgres";
10
12
  * export const db = createDb(pool, schema);
11
13
  */
12
14
  export const createDb = (pool, schema) => drizzle(pool, { schema });
15
+ /**
16
+ * Run `fn` inside a Drizzle transaction **scoped to a user**, so RLS policies
17
+ * apply on a direct connection (no PostgREST). It sets `request.jwt.claims` +
18
+ * `SET LOCAL ROLE` (default `authenticated`) as the first statement, so existing
19
+ * `auth.uid()` policies fire unchanged; the `tx` passed to `fn` is a normal
20
+ * Drizzle transaction (full query builder + `tx.execute`). Use for user-facing
21
+ * reads/writes; use plain `db` for service-role paths. The connection role must
22
+ * not bypass RLS for the rows touched — see `rls.ts`.
23
+ *
24
+ * const notes = await withRlsTransaction(db, { sub: user.id }, (tx) =>
25
+ * tx.select().from(schema.notes),
26
+ * );
27
+ */
28
+ export const withRlsTransaction = (db, claims, fn, options = {}) => db.transaction(async (tx) => {
29
+ const { role, claimsSetting, claimsJson } = resolveRlsConfig(claims, options);
30
+ await tx.execute(sql `select set_config(${claimsSetting}, ${claimsJson}, true), set_config('role', ${role}, true)`);
31
+ return fn(tx);
32
+ });
13
33
  //# sourceMappingURL=drizzle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"drizzle.js","sourceRoot":"","sources":["../src/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAuB,MAAM,2BAA2B,CAAC;AAGzE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CACvB,IAAU,EACV,MAAe,EACW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"drizzle.js","sourceRoot":"","sources":["../src/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,OAAO,EAAuB,MAAM,2BAA2B,CAAC;AAEzE,OAAO,EAAmC,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE7E;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CACvB,IAAU,EACV,MAAe,EACW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AAOxD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CACjC,EAA2B,EAC3B,MAAiB,EACjB,EAA0C,EAC1C,UAAsB,EAAE,EACX,EAAE,CACf,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;IAC3B,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9E,MAAM,EAAE,CAAC,OAAO,CACf,GAAG,CAAA,qBAAqB,aAAa,KAAK,UAAU,+BAA+B,IAAI,SAAS,CAChG,CAAC;IACF,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;AACf,CAAC,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- export { createDb } from "./drizzle.js";
1
+ export { createDb, withRlsTransaction } from "./drizzle.js";
2
2
  export { type DbEnv, dbEnv, getDatabaseUrl, isConfigured } from "./keys.js";
3
3
  export { type CreatePoolConfig, createPool } from "./pool.js";
4
4
  export { createQueries, type PoolQueries, type Queries } from "./queries.js";
5
+ export { RLS_CLAIMS_SETTING, RLS_DEFAULT_ROLE, type ResolvedRlsConfig, type RlsClaims, type RlsOptions, resolveRlsConfig, rlsPreamble, } from "./rls.js";
5
6
  export { configureTimestampsAsStrings } from "./types.js";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EACN,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,gBAAgB,EAChB,WAAW,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  // The Ingram Postgres data layer. The PGlite dev/test harness lives behind the
2
2
  // "@ingram-tech/nk-db/pglite" subpath so its WASM/dev-only deps never reach a
3
3
  // production bundle.
4
- export { createDb } from "./drizzle.js";
4
+ export { createDb, withRlsTransaction } from "./drizzle.js";
5
5
  export { dbEnv, getDatabaseUrl, isConfigured } from "./keys.js";
6
6
  export { createPool } from "./pool.js";
7
7
  export { createQueries } from "./queries.js";
8
+ export { RLS_CLAIMS_SETTING, RLS_DEFAULT_ROLE, resolveRlsConfig, rlsPreamble, } from "./rls.js";
8
9
  export { configureTimestampsAsStrings } from "./types.js";
9
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,qBAAqB;AAErB,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAc,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAyB,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAkC,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,qBAAqB;AAErB,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAc,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAyB,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAkC,MAAM,cAAc,CAAC;AAC7E,OAAO,EACN,kBAAkB,EAClB,gBAAgB,EAIhB,gBAAgB,EAChB,WAAW,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC"}
@@ -57,6 +57,14 @@ export interface TestDb {
57
57
  * An in-memory PGlite for integration tests. Real Postgres semantics, instant,
58
58
  * no cleanup. Run with Vitest `fileParallelism: false` (the socket is
59
59
  * single-connection) and close in `afterAll` (modules are isolated per file).
60
+ *
61
+ * Transport note: this harness is socket + `pg.Pool`, which is what a Next site
62
+ * dialing `DATABASE_URL` wants — but that single-connection socket is also why it
63
+ * needs serial files. A pure in-process Drizzle/PGlite harness (no socket) runs
64
+ * files in parallel and is faster, which is what an actual service reached for.
65
+ * Both share `resetPublicTables`. If a second in-process consumer appears, the
66
+ * move is to promote an in-process harness to first-class here, not to bolt one
67
+ * beside this socket default.
60
68
  */
61
69
  export declare const createTestDb: (options?: PgliteServerOptions) => Promise<TestDb>;
62
70
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pglite/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B,MAAM,WAAW,mBAAmB;IACnC,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,kBAAkB,CAAC;IAC3B,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAUD;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAC9B,UAAS,mBAAwB,KAC/B,OAAO,CAAC,YAAY,CAyBtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAC1B,UAAS,mBAAmB,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,KACzD,OAAO,CAAC,IAAI,CA0Bd,CAAC;AAEF,MAAM,WAAW,MAAM;IACtB,2EAA2E;IAC3E,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,4DAA4D;IAC5D,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACxB,UAAS,mBAAwB,KAC/B,OAAO,CAAC,MAAM,CAiBhB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pglite/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAG/B,MAAM,WAAW,mBAAmB;IACnC,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,kBAAkB,CAAC;IAC3B,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAUD;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAC9B,UAAS,mBAAwB,KAC/B,OAAO,CAAC,YAAY,CAyBtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAC1B,UAAS,mBAAmB,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,KACzD,OAAO,CAAC,IAAI,CA0Bd,CAAC;AAEF,MAAM,WAAW,MAAM;IACtB,2EAA2E;IAC3E,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,4DAA4D;IAC5D,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GACxB,UAAS,mBAAwB,KAC/B,OAAO,CAAC,MAAM,CAWhB,CAAC"}
@@ -11,6 +11,7 @@ import { existsSync } from "node:fs";
11
11
  import { rm } from "node:fs/promises";
12
12
  import { PGlite } from "@electric-sql/pglite";
13
13
  import { PGLiteSocketServer } from "@electric-sql/pglite-socket";
14
+ import { resetPublicTables } from "./reset.js";
14
15
  const defaultMigrate = (migrationsFolder) => async (db) => {
15
16
  const { drizzle } = await import("drizzle-orm/pglite");
16
17
  const { migrate } = await import("drizzle-orm/pglite/migrator");
@@ -75,18 +76,20 @@ export const startPgliteDev = async (options = {}) => {
75
76
  * An in-memory PGlite for integration tests. Real Postgres semantics, instant,
76
77
  * no cleanup. Run with Vitest `fileParallelism: false` (the socket is
77
78
  * single-connection) and close in `afterAll` (modules are isolated per file).
79
+ *
80
+ * Transport note: this harness is socket + `pg.Pool`, which is what a Next site
81
+ * dialing `DATABASE_URL` wants — but that single-connection socket is also why it
82
+ * needs serial files. A pure in-process Drizzle/PGlite harness (no socket) runs
83
+ * files in parallel and is faster, which is what an actual service reached for.
84
+ * Both share `resetPublicTables`. If a second in-process consumer appears, the
85
+ * move is to promote an in-process harness to first-class here, not to bolt one
86
+ * beside this socket default.
78
87
  */
79
88
  export const createTestDb = async (options = {}) => {
80
89
  const { Pool } = await import("pg");
81
90
  const { databaseUrl, stop } = await createPgliteServer(options);
82
91
  const pool = new Pool({ connectionString: databaseUrl, max: 1 });
83
- const reset = async () => {
84
- const result = await pool.query("select tablename from pg_tables where schemaname = 'public'");
85
- if (result.rows.length === 0)
86
- return;
87
- const list = result.rows.map((row) => `"${row.tablename}"`).join(", ");
88
- await pool.query(`truncate table ${list} restart identity cascade`);
89
- };
92
+ const reset = () => resetPublicTables((sql) => pool.query(sql));
90
93
  const close = async () => {
91
94
  await pool.end();
92
95
  await stop();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pglite/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pglite/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A row-returning query — `pg.Pool.query`, raw PGlite `.query`, etc. The
3
+ * `pg_tables` introspection reads rows back, so this can't be a fire-and-forget
4
+ * `exec(sql)`: the contract has to surface `{ rows }`.
5
+ */
6
+ export type Queryable = (sql: string) => Promise<{
7
+ rows: {
8
+ tablename: string;
9
+ }[];
10
+ }>;
11
+ /**
12
+ * TRUNCATE every table in the `public` schema, resetting identity sequences.
13
+ * Call in a Vitest `beforeEach`. No-op when the schema is empty (e.g. before
14
+ * migrations have run), so it's safe to call unconditionally.
15
+ */
16
+ export declare function resetPublicTables(query: Queryable): Promise<void>;
17
+ //# sourceMappingURL=reset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../src/pglite/reset.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC,CAAC;AAEpF;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAOvE"}
@@ -0,0 +1,23 @@
1
+ // Reset a PGlite/Postgres test database to empty between tests — the canonical
2
+ // "introspect public tables, then TRUNCATE … RESTART IDENTITY CASCADE" unit,
3
+ // kept transport-agnostic so the socket-backed `createTestDb` and any in-process
4
+ // Drizzle/PGlite harness share one implementation instead of each re-deriving the
5
+ // SQL (and the easy-to-forget empty-list guard) and drifting apart.
6
+ //
7
+ // Deliberately zero imports: this loads via the dedicated
8
+ // "@ingram-tech/nk-db/pglite/reset" subpath, so an in-process consumer gets the
9
+ // reset logic WITHOUT dragging in PGlite or the PGLiteSocketServer that
10
+ // `./index.ts` imports at the top level.
11
+ /**
12
+ * TRUNCATE every table in the `public` schema, resetting identity sequences.
13
+ * Call in a Vitest `beforeEach`. No-op when the schema is empty (e.g. before
14
+ * migrations have run), so it's safe to call unconditionally.
15
+ */
16
+ export async function resetPublicTables(query) {
17
+ const { rows } = await query("select tablename from pg_tables where schemaname = 'public'");
18
+ if (rows.length === 0)
19
+ return;
20
+ const list = rows.map((row) => `"${row.tablename}"`).join(", ");
21
+ await query(`truncate table ${list} restart identity cascade`);
22
+ }
23
+ //# sourceMappingURL=reset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.js","sourceRoot":"","sources":["../../src/pglite/reset.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAC7E,iFAAiF;AACjF,kFAAkF;AAClF,oEAAoE;AACpE,EAAE;AACF,0DAA0D;AAC1D,gFAAgF;AAChF,wEAAwE;AACxE,yCAAyC;AASzC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAgB;IACvD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAC3B,6DAA6D,CAC7D,CAAC;IACF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChE,MAAM,KAAK,CAAC,kBAAkB,IAAI,2BAA2B,CAAC,CAAC;AAChE,CAAC"}
package/dist/queries.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Pool, QueryResultRow } from "pg";
2
+ import { type RlsClaims, type RlsOptions } from "./rls.js";
2
3
  export interface Queries {
3
4
  /** Run a query and return all rows. */
4
5
  query: <T extends QueryResultRow = QueryResultRow>(text: string, params?: unknown[]) => Promise<T[]>;
@@ -17,6 +18,15 @@ export interface PoolQueries extends Queries {
17
18
  * statements that must be atomic.
18
19
  */
19
20
  withTx: <T>(fn: (tx: Queries) => Promise<T>) => Promise<T>;
21
+ /**
22
+ * Like `withTx`, but first **scopes the transaction to a user** so RLS
23
+ * policies apply: it sets `request.jwt.claims` + `SET LOCAL ROLE` (default
24
+ * `authenticated`) before `fn`, reproducing what PostgREST did. Use this for
25
+ * user-facing reads/writes on a direct connection; keep `withTx` / `query`
26
+ * for service-role paths that should bypass RLS. The connection role must not
27
+ * bypass RLS for the rows touched — see `rls.ts`.
28
+ */
29
+ withRls: <T>(claims: RlsClaims, fn: (tx: Queries) => Promise<T>, options?: RlsOptions) => Promise<T>;
20
30
  }
21
31
  /**
22
32
  * Bind the raw-SQL helpers (`query` / `one` / `maybeOne` / `execute` / `withTx`)
@@ -1 +1 @@
1
- {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,cAAc,EAAE,MAAM,IAAI,CAAC;AAK3D,MAAM,WAAW,OAAO;IACvB,uCAAuC;IACvC,KAAK,EAAE,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EAChD,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,KACd,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAClB,wEAAwE;IACxE,QAAQ,EAAE,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EACnD,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,KACd,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACvB,sEAAsE;IACtE,GAAG,EAAE,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EAC9C,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,KACd,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB,qDAAqD;IACrD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/D;AAoCD,MAAM,WAAW,WAAY,SAAQ,OAAO;IAC3C;;;;;OAKG;IACH,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAC3D;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,IAAI,KAAG,WAiB1C,CAAC"}
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,cAAc,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EACN,KAAK,SAAS,EACd,KAAK,UAAU,EAGf,MAAM,UAAU,CAAC;AAKlB,MAAM,WAAW,OAAO;IACvB,uCAAuC;IACvC,KAAK,EAAE,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EAChD,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,KACd,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAClB,wEAAwE;IACxE,QAAQ,EAAE,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EACnD,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,KACd,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACvB,sEAAsE;IACtE,GAAG,EAAE,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EAC9C,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,KACd,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB,qDAAqD;IACrD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/D;AAoCD,MAAM,WAAW,WAAY,SAAQ,OAAO;IAC3C;;;;;OAKG;IACH,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3D;;;;;;;OAOG;IACH,OAAO,EAAE,CAAC,CAAC,EACV,MAAM,EAAE,SAAS,EACjB,EAAE,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,EAC/B,OAAO,CAAC,EAAE,UAAU,KAChB,OAAO,CAAC,CAAC,CAAC,CAAC;CAChB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,IAAI,KAAG,WAkC1C,CAAC"}
package/dist/queries.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { resolveRlsConfig, rlsPreamble, } from "./rls.js";
1
2
  const bind = (executor) => {
2
3
  const query = async (text, params = []) => {
3
4
  const result = await executor.query(text, params);
@@ -32,10 +33,14 @@ const bind = (executor) => {
32
33
  */
33
34
  export const createQueries = (pool) => {
34
35
  const base = bind(pool);
35
- const withTx = async (fn) => {
36
+ // One transaction runner; `setup` (if any) runs after `begin`, before `fn` —
37
+ // that's where withRls injects the claims/role GUCs.
38
+ const runTx = async (setup, fn) => {
36
39
  const client = await pool.connect();
37
40
  try {
38
41
  await client.query("begin");
42
+ if (setup)
43
+ await setup(client);
39
44
  const result = await fn(bind(client));
40
45
  await client.query("commit");
41
46
  return result;
@@ -48,6 +53,11 @@ export const createQueries = (pool) => {
48
53
  client.release();
49
54
  }
50
55
  };
51
- return { ...base, withTx };
56
+ const withTx = (fn) => runTx(undefined, fn);
57
+ const withRls = (claims, fn, options) => runTx(async (client) => {
58
+ const { text, values } = rlsPreamble(resolveRlsConfig(claims, options));
59
+ await client.query(text, values);
60
+ }, fn);
61
+ return { ...base, withTx, withRls };
52
62
  };
53
63
  //# sourceMappingURL=queries.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAyBA,MAAM,IAAI,GAAG,CAAC,QAAkB,EAAW,EAAE;IAC5C,MAAM,KAAK,GAAG,KAAK,EAClB,IAAY,EACZ,SAAoB,EAAE,EACP,EAAE;QACjB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;QACrD,OAAO,MAAM,CAAC,IAAI,CAAC;IACpB,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,KAAK,EACrB,IAAY,EACZ,SAAoB,EAAE,EACF,EAAE;QACtB,MAAM,IAAI,GAAG,MAAM,KAAK,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACxB,CAAC,CAAC;IACF,MAAM,GAAG,GAAG,KAAK,EAChB,IAAY,EACZ,SAAoB,EAAE,EACT,EAAE;QACf,MAAM,IAAI,GAAG,MAAM,KAAK,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QAChE,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,GAAG,CAAC;IACZ,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,KAAK,EAAE,IAAY,EAAE,SAAoB,EAAE,EAAmB,EAAE;QAC/E,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAClD,OAAO,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAC1C,CAAC,CAAC;AAYF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAU,EAAe,EAAE;IACxD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,MAAM,MAAM,GAAG,KAAK,EAAK,EAA+B,EAAc,EAAE;QACvE,MAAM,MAAM,GAAe,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAChD,IAAI,CAAC;YACJ,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YACtC,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO,MAAM,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/B,MAAM,KAAK,CAAC;QACb,CAAC;gBAAS,CAAC;YACV,MAAM,CAAC,OAAO,EAAE,CAAC;QAClB,CAAC;IACF,CAAC,CAAC;IACF,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC,CAAC"}
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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"}
package/dist/rls.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Row-Level Security for a **direct Postgres connection** (no PostgREST).
3
+ *
4
+ * On Supabase, PostgREST is what made RLS work: per request it did
5
+ * `SET ROLE authenticated` and set `request.jwt.claims` from the user's JWT, so
6
+ * policies written against `auth.uid()` fired. The moment a site queries Postgres
7
+ * directly (`pg` / Drizzle) — whether it's still on Supabase Postgres or has
8
+ * moved to our DigitalOcean cluster — that setup is gone, and a plain connection
9
+ * runs as the connection's role with **no claims**, so RLS is either bypassed
10
+ * (privileged role) or denies everything.
11
+ *
12
+ * These helpers reproduce exactly what PostgREST did, per transaction: set the
13
+ * claims GUC and `SET LOCAL ROLE`, so **existing `auth.uid()` policies keep
14
+ * working unchanged**. It is pure Postgres, so it behaves identically on Supabase
15
+ * and on DO. See `docs/db-package.md` (§RLS) and `docs/better-auth-migration.md`.
16
+ *
17
+ * Two requirements the caller owns (documented, not enforceable here):
18
+ * 1. The pool must connect as a role that **does not bypass RLS** for the rows
19
+ * it touches — i.e. not the table owner and not a `BYPASSRLS` superuser for
20
+ * user-facing reads. (After `SET ROLE authenticated`, RLS applies even if the
21
+ * underlying connection is `postgres`, exactly as PostgREST relied on.)
22
+ * 2. The connecting role must be allowed to `SET ROLE` to the target role
23
+ * (Supabase's `authenticator`/`postgres` can; on DO, `GRANT app_user TO …`).
24
+ */
25
+ /**
26
+ * The JWT-style claims to scope a transaction by. `sub` becomes `auth.uid()`;
27
+ * `role` (default `"authenticated"`) is the Postgres role assumed. Any further
28
+ * keys are written into the claims GUC for policies that read them
29
+ * (`request.jwt.claims ->> 'org_id'`, etc.).
30
+ */
31
+ export interface RlsClaims {
32
+ /**
33
+ * The user id. Becomes the `sub` claim → `auth.uid()`. For Supabase-style
34
+ * policies that cast `sub` to `uuid`, this MUST be a valid UUID.
35
+ */
36
+ sub: string;
37
+ /** The Postgres role to assume (the JWT `role` claim). Defaults to `"authenticated"`. */
38
+ role?: string;
39
+ /** Any further claims your policies read from `request.jwt.claims`. */
40
+ [claim: string]: unknown;
41
+ }
42
+ /** Overrides for {@link resolveRlsConfig} (and the helpers that build on it). */
43
+ export interface RlsOptions {
44
+ /**
45
+ * Force the Postgres role to `SET LOCAL`, ignoring `claims.role`. Use when the
46
+ * DB role name differs from the JWT `role` claim (e.g. a dedicated `app_user`
47
+ * on DO while the claim stays `"authenticated"`).
48
+ */
49
+ role?: string;
50
+ /** The GUC the claims JSON is written to. Defaults to `"request.jwt.claims"`. */
51
+ claimsSetting?: string;
52
+ }
53
+ /** The role assumed when neither `options.role` nor `claims.role` is set. */
54
+ export declare const RLS_DEFAULT_ROLE = "authenticated";
55
+ /** The GUC `auth.uid()` / `auth.role()` read; what PostgREST populated. */
56
+ export declare const RLS_CLAIMS_SETTING = "request.jwt.claims";
57
+ /** The concrete values a transaction is scoped with. */
58
+ export interface ResolvedRlsConfig {
59
+ /** Postgres role to assume for the transaction. */
60
+ role: string;
61
+ /** GUC the claims JSON is written to. */
62
+ claimsSetting: string;
63
+ /** The claims serialized for `set_config` (a JSON string). */
64
+ claimsJson: string;
65
+ }
66
+ /** Resolve claims + options into the role, GUC name, and serialized claims. */
67
+ export declare const resolveRlsConfig: (claims: RlsClaims, options?: RlsOptions) => ResolvedRlsConfig;
68
+ /**
69
+ * The single parameterized statement that scopes a transaction: writes the
70
+ * claims GUC and the `role` GUC, **both transaction-local** (`is_local = true`,
71
+ * so they reset at `commit`/`rollback` and never leak across pooled
72
+ * connections). Everything — including the GUC name and role — is bound, never
73
+ * interpolated, so it's injection-safe.
74
+ *
75
+ * Exposed for callers that manage their own connection/transaction (e.g. a
76
+ * framework middleware): run it as the first statement after `begin`.
77
+ */
78
+ export declare const rlsPreamble: (config: ResolvedRlsConfig) => {
79
+ text: string;
80
+ values: string[];
81
+ };
82
+ //# sourceMappingURL=rls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls.d.ts","sourceRoot":"","sources":["../src/rls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,yFAAyF;IACzF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB;AAED,iFAAiF;AACjF,MAAM,WAAW,UAAU;IAC1B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAChD,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD,wDAAwD;AACxD,MAAM,WAAW,iBAAiB;IACjC,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,GAC5B,QAAQ,SAAS,EACjB,UAAS,UAAe,KACtB,iBAID,CAAC;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GACvB,QAAQ,iBAAiB,KACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAGjC,CAAC"}
package/dist/rls.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Row-Level Security for a **direct Postgres connection** (no PostgREST).
3
+ *
4
+ * On Supabase, PostgREST is what made RLS work: per request it did
5
+ * `SET ROLE authenticated` and set `request.jwt.claims` from the user's JWT, so
6
+ * policies written against `auth.uid()` fired. The moment a site queries Postgres
7
+ * directly (`pg` / Drizzle) — whether it's still on Supabase Postgres or has
8
+ * moved to our DigitalOcean cluster — that setup is gone, and a plain connection
9
+ * runs as the connection's role with **no claims**, so RLS is either bypassed
10
+ * (privileged role) or denies everything.
11
+ *
12
+ * These helpers reproduce exactly what PostgREST did, per transaction: set the
13
+ * claims GUC and `SET LOCAL ROLE`, so **existing `auth.uid()` policies keep
14
+ * working unchanged**. It is pure Postgres, so it behaves identically on Supabase
15
+ * and on DO. See `docs/db-package.md` (§RLS) and `docs/better-auth-migration.md`.
16
+ *
17
+ * Two requirements the caller owns (documented, not enforceable here):
18
+ * 1. The pool must connect as a role that **does not bypass RLS** for the rows
19
+ * it touches — i.e. not the table owner and not a `BYPASSRLS` superuser for
20
+ * user-facing reads. (After `SET ROLE authenticated`, RLS applies even if the
21
+ * underlying connection is `postgres`, exactly as PostgREST relied on.)
22
+ * 2. The connecting role must be allowed to `SET ROLE` to the target role
23
+ * (Supabase's `authenticator`/`postgres` can; on DO, `GRANT app_user TO …`).
24
+ */
25
+ /** The role assumed when neither `options.role` nor `claims.role` is set. */
26
+ export const RLS_DEFAULT_ROLE = "authenticated";
27
+ /** The GUC `auth.uid()` / `auth.role()` read; what PostgREST populated. */
28
+ export const RLS_CLAIMS_SETTING = "request.jwt.claims";
29
+ /** Resolve claims + options into the role, GUC name, and serialized claims. */
30
+ export const resolveRlsConfig = (claims, options = {}) => ({
31
+ role: options.role ?? claims.role ?? RLS_DEFAULT_ROLE,
32
+ claimsSetting: options.claimsSetting ?? RLS_CLAIMS_SETTING,
33
+ claimsJson: JSON.stringify(claims),
34
+ });
35
+ /**
36
+ * The single parameterized statement that scopes a transaction: writes the
37
+ * claims GUC and the `role` GUC, **both transaction-local** (`is_local = true`,
38
+ * so they reset at `commit`/`rollback` and never leak across pooled
39
+ * connections). Everything — including the GUC name and role — is bound, never
40
+ * interpolated, so it's injection-safe.
41
+ *
42
+ * Exposed for callers that manage their own connection/transaction (e.g. a
43
+ * framework middleware): run it as the first statement after `begin`.
44
+ */
45
+ export const rlsPreamble = (config) => ({
46
+ text: "select set_config($1, $2, true), set_config('role', $3, true)",
47
+ values: [config.claimsSetting, config.claimsJson, config.role],
48
+ });
49
+ //# sourceMappingURL=rls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls.js","sourceRoot":"","sources":["../src/rls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAgCH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,gBAAgB,GAAG,eAAe,CAAC;AAChD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AAYvD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC/B,MAAiB,EACjB,UAAsB,EAAE,EACJ,EAAE,CAAC,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,gBAAgB;IACrD,aAAa,EAAE,OAAO,CAAC,aAAa,IAAI,kBAAkB;IAC1D,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;CAClC,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAC1B,MAAyB,EACY,EAAE,CAAC,CAAC;IACzC,IAAI,EAAE,+DAA+D;IACrE,MAAM,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC;CAC9D,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingram-tech/nk-db",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "The Ingram Postgres data layer: one TLS-aware pg pool, raw-SQL helpers, Drizzle wiring, and a PGlite (no-Docker) dev/test harness for Next.js sites.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -26,6 +26,10 @@
26
26
  "./pglite": {
27
27
  "types": "./dist/pglite/index.d.ts",
28
28
  "import": "./dist/pglite/index.js"
29
+ },
30
+ "./pglite/reset": {
31
+ "types": "./dist/pglite/reset.d.ts",
32
+ "import": "./dist/pglite/reset.js"
29
33
  }
30
34
  },
31
35
  "scripts": {
@@ -38,7 +42,7 @@
38
42
  },
39
43
  "peerDependencies": {
40
44
  "@electric-sql/pglite": ">=0.5.0",
41
- "@electric-sql/pglite-socket": ">=0.0.10",
45
+ "@electric-sql/pglite-socket": ">=0.2.4",
42
46
  "drizzle-orm": ">=0.44.0",
43
47
  "pg": "^8.13.0"
44
48
  },
@@ -52,9 +56,9 @@
52
56
  },
53
57
  "devDependencies": {
54
58
  "@electric-sql/pglite": "^0.5.0",
55
- "@electric-sql/pglite-socket": "^0.0.10",
56
- "@ingram-tech/typescript-config": "workspace:*",
57
- "@types/node": "^20.0.0",
59
+ "@electric-sql/pglite-socket": "^0.2.4",
60
+ "@ingram-tech/typescript-config": "0.1.0",
61
+ "@types/node": "^25.0.0",
58
62
  "@types/pg": "^8.11.0",
59
63
  "drizzle-orm": "^0.45.0",
60
64
  "pg": "^8.13.0",