@nodii/db-rls 0.0.1 → 0.2.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.
Files changed (50) hide show
  1. package/bin/migrate-gen.mjs +12 -0
  2. package/dist/auth-schema.d.ts +45 -0
  3. package/dist/auth-schema.d.ts.map +1 -0
  4. package/dist/auth-schema.js +101 -0
  5. package/dist/auth-schema.js.map +1 -0
  6. package/dist/configure.d.ts +45 -0
  7. package/dist/configure.d.ts.map +1 -0
  8. package/dist/configure.js +85 -0
  9. package/dist/configure.js.map +1 -0
  10. package/dist/context.d.ts +43 -0
  11. package/dist/context.d.ts.map +1 -0
  12. package/dist/context.js +98 -0
  13. package/dist/context.js.map +1 -0
  14. package/dist/drizzle-client.d.ts +22 -0
  15. package/dist/drizzle-client.d.ts.map +1 -0
  16. package/dist/drizzle-client.js +109 -0
  17. package/dist/drizzle-client.js.map +1 -0
  18. package/dist/in-memory-pool.d.ts +31 -0
  19. package/dist/in-memory-pool.d.ts.map +1 -0
  20. package/dist/in-memory-pool.js +53 -0
  21. package/dist/in-memory-pool.js.map +1 -0
  22. package/dist/index.d.ts +21 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +35 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/init.d.ts +18 -0
  27. package/dist/init.d.ts.map +1 -0
  28. package/dist/init.js +40 -0
  29. package/dist/init.js.map +1 -0
  30. package/dist/migrate-gen.d.ts +41 -0
  31. package/dist/migrate-gen.d.ts.map +1 -0
  32. package/dist/migrate-gen.js +123 -0
  33. package/dist/migrate-gen.js.map +1 -0
  34. package/dist/pg-pool-adapter.d.ts +44 -0
  35. package/dist/pg-pool-adapter.d.ts.map +1 -0
  36. package/dist/pg-pool-adapter.js +111 -0
  37. package/dist/pg-pool-adapter.js.map +1 -0
  38. package/dist/rls-policy-gen.d.ts +26 -0
  39. package/dist/rls-policy-gen.d.ts.map +1 -0
  40. package/dist/rls-policy-gen.js +81 -0
  41. package/dist/rls-policy-gen.js.map +1 -0
  42. package/dist/rls-policy.d.ts +30 -0
  43. package/dist/rls-policy.d.ts.map +1 -0
  44. package/dist/rls-policy.js +81 -0
  45. package/dist/rls-policy.js.map +1 -0
  46. package/dist/types.d.ts +71 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +56 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +31 -7
@@ -0,0 +1,53 @@
1
+ // In-memory PgPool / PgTx test doubles.
2
+ //
3
+ // Records every `set_config` call and every `exec` invocation so tests
4
+ // can assert the exact transcript without needing a real Postgres.
5
+ // Production wiring (pg / Drizzle / psycopg / pgx) lands in v0.1.x —
6
+ // `// TODO db-rls-v0.1.x: real driver adapters`.
7
+ /** In-memory PgPool. Each instance owns its own transcript so tests can
8
+ * inspect what each pool was asked to do. */
9
+ export class InMemoryPgPool {
10
+ kind;
11
+ transcript = [];
12
+ constructor(kind) {
13
+ this.kind = kind;
14
+ }
15
+ async withTransaction(fn) {
16
+ this.transcript.push({ kind: "begin", pool: this.kind });
17
+ const tx = new InMemoryPgTx(this);
18
+ try {
19
+ const result = await fn(tx);
20
+ this.transcript.push({ kind: "commit", pool: this.kind });
21
+ return result;
22
+ }
23
+ catch (err) {
24
+ this.transcript.push({ kind: "rollback", pool: this.kind });
25
+ throw err;
26
+ }
27
+ }
28
+ }
29
+ class InMemoryPgTx {
30
+ poolKind;
31
+ pool;
32
+ constructor(pool) {
33
+ this.pool = pool;
34
+ this.poolKind = pool.kind;
35
+ }
36
+ async exec(sql, params = []) {
37
+ this.pool.transcript.push({
38
+ kind: "exec",
39
+ pool: this.poolKind,
40
+ sql,
41
+ params,
42
+ });
43
+ }
44
+ async setConfig(key, value) {
45
+ this.pool.transcript.push({
46
+ kind: "set_config",
47
+ pool: this.poolKind,
48
+ key,
49
+ value,
50
+ });
51
+ }
52
+ }
53
+ //# sourceMappingURL=in-memory-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-pool.js","sourceRoot":"","sources":["../src/in-memory-pool.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,uEAAuE;AACvE,mEAAmE;AACnE,qEAAqE;AACrE,iDAAiD;AAYjD;8CAC8C;AAC9C,MAAM,OAAO,cAAc;IAChB,IAAI,CAAW;IACf,UAAU,GAAsB,EAAE,CAAC;IAE5C,YAAY,IAAc;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,eAAe,CAAI,EAA4B;QACnD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACzD,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;YAC5B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1D,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAC5D,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AAED,MAAM,YAAY;IACP,QAAQ,CAAW;IACX,IAAI,CAAiB;IAEtC,YAAY,IAAoB;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,SAA6B,EAAE;QACrD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YACxB,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,IAAI,CAAC,QAAQ;YACnB,GAAG;YACH,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAW,EAAE,KAAa;QACxC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YACxB,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,IAAI,CAAC,QAAQ;YACnB,GAAG;YACH,KAAK;SACN,CAAC,CAAC;IACL,CAAC;CACF"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,23 @@
1
1
  export declare const LIB_NAME = "db-rls";
2
- export declare const VERSION = "0.0.1";
2
+ export declare const VERSION = "0.1.1";
3
+ export { initDbRls, requireDbRls, getDbRlsOrNull, requirePool, _resetForTests, } from "./init";
4
+ export type { InitDbRlsConfig, ResolvedDbRls } from "./init";
5
+ export { configureDbRls, PoolCredentialMismatch, _resetConfigureForTests, } from "./configure";
6
+ export type { ConfigureDbRlsOpts } from "./configure";
7
+ export { PgClientPool } from "./pg-pool-adapter";
8
+ export type { PgPoolAdapterOpts } from "./pg-pool-adapter";
9
+ export { withUserContext, withSystemContext, withOwnerContext, autoDispatch, } from "./context";
10
+ export { withUserContextDb, withSystemContextDb, withOwnerContextDb, } from "./drizzle-client";
11
+ export type { DrizzleClient, NodePgDatabase } from "./drizzle-client";
12
+ export { AUTH_SCHEMA_SQL, AUTH_SCHEMA_GRANTS_SQL, applyAuthSchemaMigrations, authSchemaFullSql, } from "./auth-schema";
13
+ export type { SqlExecutor } from "./auth-schema";
14
+ export { withRls, generateTableGrants } from "./rls-policy-gen";
15
+ export type { RlsVerb, WithRlsOpts } from "./rls-policy-gen";
16
+ export { migrateGen, runMigrateGenCli, userTenantIdsRealSql, userTenantIdsStubSql, } from "./migrate-gen";
17
+ export type { MigrateGenInput, MigrateGenResult } from "./migrate-gen";
18
+ export { generateRlsPolicy, authSchemaHelpers } from "./rls-policy";
19
+ export { InMemoryPgPool } from "./in-memory-pool";
20
+ export type { TranscriptEntry } from "./in-memory-pool";
21
+ export { DbRlsError, DbRlsNotInitialized, PoolUnavailable, MissingTenantContext, TransactionRolledBack, } from "./types";
22
+ export type { AuthContext, AutoDispatchOpts, GenerateRlsPolicyOpts, PgPool, PgTx, PoolKind, SystemContextOpts, UserContextOpts, } from "./types";
3
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,QAAQ,WAAW,CAAC;AACjC,eAAO,MAAM,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,QAAQ,WAAW,CAAC;AACjC,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EACL,SAAS,EACT,YAAY,EACZ,cAAc,EACd,WAAW,EACX,cAAc,GACf,MAAM,QAAQ,CAAC;AAChB,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAE7D,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,GACb,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAChE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAIvE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EACL,UAAU,EACV,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,qBAAqB,EACrB,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,iBAAiB,EACjB,eAAe,GAChB,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,38 @@
1
- // @nodii/db-rls — placeholder at v0.0.1.
2
- // Implementation lands at v0.1.0 per the locked feature_doc.
1
+ // @nodii/db-rls — v0.1.1 public barrel.
3
2
  //
4
- // Spec: https://planning.dev.nucleus-cloud.in/api/v1/feature-docs?serviceId=nodii-libs&docKey=db-rls
3
+ // Spec: planning hub feature_doc serviceId=nodii-libs docKey=db-rls
4
+ // (A1 posture — RLS on every tenant-scoped table — locked 2026-05-25).
5
+ // Polyglot ship: TS + Python + Go in parity.
6
+ //
7
+ // v0.1.1 surface:
8
+ // - configureDbRls (real pg.Pool-aware boot helper) + initDbRls (raw)
9
+ // - PgClientPool (wraps pg.Pool → PgPool interface)
10
+ // - InMemoryPgPool (still exported — used by upstream tests)
11
+ // - withUserContext / withSystemContext / withOwnerContext / autoDispatch
12
+ // (operates on the substrate-agnostic PgTx)
13
+ // - withUserContextDb / withSystemContextDb / withOwnerContextDb
14
+ // (Drizzle-bound; require a real PgClientPool)
15
+ // - applyAuthSchemaMigrations + AUTH_SCHEMA_SQL + AUTH_SCHEMA_GRANTS_SQL
16
+ // - withRls() (RLS-policy DDL generator per § 5.5)
17
+ // - generateRlsPolicy / authSchemaHelpers (deprecated aliases, kept for
18
+ // parity-fence + parity with the v0.1.0 surface; no NotImplemented paths)
19
+ // - migrateGen + runMigrateGenCli + userTenantIdsRealSql + userTenantIdsStubSql
20
+ // - Typed errors: DbRlsError + DbRlsNotInitialized + PoolUnavailable +
21
+ // MissingTenantContext + TransactionRolledBack +
22
+ // PoolCredentialMismatch
5
23
  export const LIB_NAME = "db-rls";
6
- export const VERSION = "0.0.1";
24
+ export const VERSION = "0.1.1";
25
+ export { initDbRls, requireDbRls, getDbRlsOrNull, requirePool, _resetForTests, } from "./init";
26
+ export { configureDbRls, PoolCredentialMismatch, _resetConfigureForTests, } from "./configure";
27
+ export { PgClientPool } from "./pg-pool-adapter";
28
+ export { withUserContext, withSystemContext, withOwnerContext, autoDispatch, } from "./context";
29
+ export { withUserContextDb, withSystemContextDb, withOwnerContextDb, } from "./drizzle-client";
30
+ export { AUTH_SCHEMA_SQL, AUTH_SCHEMA_GRANTS_SQL, applyAuthSchemaMigrations, authSchemaFullSql, } from "./auth-schema";
31
+ export { withRls, generateTableGrants } from "./rls-policy-gen";
32
+ export { migrateGen, runMigrateGenCli, userTenantIdsRealSql, userTenantIdsStubSql, } from "./migrate-gen";
33
+ // Legacy v0.1.0 surface kept for callers that already imported these
34
+ // names. Both are byte-equal SQL helpers — no NotImplemented paths.
35
+ export { generateRlsPolicy, authSchemaHelpers } from "./rls-policy";
36
+ export { InMemoryPgPool } from "./in-memory-pool";
37
+ export { DbRlsError, DbRlsNotInitialized, PoolUnavailable, MissingTenantContext, TransactionRolledBack, } from "./types";
7
38
  //# 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,yCAAyC;AACzC,6DAA6D;AAC7D,EAAE;AACF,qGAAqG;AAErG,MAAM,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC;AACjC,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,oEAAoE;AACpE,uEAAuE;AACvE,6CAA6C;AAC7C,EAAE;AACF,kBAAkB;AAClB,wEAAwE;AACxE,sDAAsD;AACtD,+DAA+D;AAC/D,4EAA4E;AAC5E,gDAAgD;AAChD,mEAAmE;AACnE,mDAAmD;AACnD,2EAA2E;AAC3E,qDAAqD;AACrD,0EAA0E;AAC1E,8EAA8E;AAC9E,kFAAkF;AAClF,yEAAyE;AACzE,mEAAmE;AACnE,2CAA2C;AAE3C,MAAM,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC;AACjC,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EACL,SAAS,EACT,YAAY,EACZ,cAAc,EACd,WAAW,EACX,cAAc,GACf,MAAM,QAAQ,CAAC;AAGhB,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,GACb,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AAGvB,qEAAqE;AACrE,oEAAoE;AACpE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EACL,UAAU,EACV,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,SAAS,CAAC"}
package/dist/init.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { type PgPool, type PoolKind } from "./types";
2
+ export interface InitDbRlsConfig {
3
+ owner: PgPool;
4
+ services: PgPool;
5
+ authenticated: PgPool;
6
+ }
7
+ export interface ResolvedDbRls {
8
+ owner: PgPool;
9
+ services: PgPool;
10
+ authenticated: PgPool;
11
+ }
12
+ export declare function initDbRls(config: InitDbRlsConfig): void;
13
+ export declare function requireDbRls(): ResolvedDbRls;
14
+ export declare function getDbRlsOrNull(): ResolvedDbRls | null;
15
+ export declare function requirePool(kind: PoolKind): PgPool;
16
+ /** Test-only reset; NOT exported through the public barrel. */
17
+ export declare function _resetForTests(): void;
18
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,MAAM,EAEX,KAAK,QAAQ,EACd,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAID,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAcvD;AAED,wBAAgB,YAAY,IAAI,aAAa,CAK5C;AAED,wBAAgB,cAAc,IAAI,aAAa,GAAG,IAAI,CAErD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAOlD;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,IAAI,IAAI,CAErC"}
package/dist/init.js ADDED
@@ -0,0 +1,40 @@
1
+ // initDbRls — process-local bootstrap; mirrors initSaga / initTelemetry.
2
+ //
3
+ // Spec § 5.2. Caller supplies one PgPool per role. Subsequent calls warn +
4
+ // no-op (first-init wins). Public APIs throw `DbRlsNotInitialized` before init.
5
+ import { DbRlsNotInitialized, PoolUnavailable, } from "./types";
6
+ let resolved = null;
7
+ export function initDbRls(config) {
8
+ if (resolved) {
9
+ // eslint-disable-next-line no-console
10
+ console.warn("[@nodii/db-rls] initDbRls() called more than once; ignoring subsequent call.");
11
+ return;
12
+ }
13
+ resolved = {
14
+ owner: config.owner,
15
+ services: config.services,
16
+ authenticated: config.authenticated,
17
+ };
18
+ }
19
+ export function requireDbRls() {
20
+ if (!resolved) {
21
+ throw new DbRlsNotInitialized();
22
+ }
23
+ return resolved;
24
+ }
25
+ export function getDbRlsOrNull() {
26
+ return resolved;
27
+ }
28
+ export function requirePool(kind) {
29
+ const cfg = requireDbRls();
30
+ const pool = cfg[kind];
31
+ if (!pool) {
32
+ throw new PoolUnavailable(kind);
33
+ }
34
+ return pool;
35
+ }
36
+ /** Test-only reset; NOT exported through the public barrel. */
37
+ export function _resetForTests() {
38
+ resolved = null;
39
+ }
40
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,2EAA2E;AAC3E,gFAAgF;AAEhF,OAAO,EACL,mBAAmB,EAEnB,eAAe,GAEhB,MAAM,SAAS,CAAC;AAcjB,IAAI,QAAQ,GAAyB,IAAI,CAAC;AAE1C,MAAM,UAAU,SAAS,CAAC,MAAuB;IAC/C,IAAI,QAAQ,EAAE,CAAC;QACb,sCAAsC;QACtC,OAAO,CAAC,IAAI,CACV,8EAA8E,CAC/E,CAAC;QACF,OAAO;IACT,CAAC;IAED,QAAQ,GAAG;QACT,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,aAAa,EAAE,MAAM,CAAC,aAAa;KACpC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,mBAAmB,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAc;IACxC,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,cAAc;IAC5B,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC"}
@@ -0,0 +1,41 @@
1
+ export interface MigrateGenInput {
2
+ /** Pattern A service name, e.g. "nodii-billing-service". Used only for
3
+ * the file header comment; not in the SQL body. */
4
+ serviceName: string;
5
+ /** Defaults to `./infra/postgres`. */
6
+ outDir?: string;
7
+ /** When `true`, emit ONLY the stub variant (returns empty set). Use
8
+ * when running in the chicken-and-egg case where Drizzle migrate has
9
+ * not yet created the mirror tables. */
10
+ stub?: boolean;
11
+ }
12
+ export interface MigrateGenResult {
13
+ /** Absolute path of the file that was written. */
14
+ path: string;
15
+ /** Contents written (also returned for parity-fence assertions). */
16
+ contents: string;
17
+ }
18
+ /**
19
+ * SQL text for the real (joins mirror tables) helper. Module-level
20
+ * constant so parity-fence can byte-compare.
21
+ */
22
+ export declare function userTenantIdsRealSql(serviceName: string): string;
23
+ /**
24
+ * SQL text for the chicken-and-egg stub variant (returns empty set).
25
+ */
26
+ export declare function userTenantIdsStubSql(serviceName: string): string;
27
+ /**
28
+ * Generates the residual migration phase file and returns its contents.
29
+ * If `outDir` is set, writes the file to disk; otherwise just returns
30
+ * the contents (used by tests + parity-fence).
31
+ */
32
+ export declare function migrateGen(input: MigrateGenInput): MigrateGenResult;
33
+ /**
34
+ * CLI entrypoint. Invoked via `npx @nodii/db-rls migrate-gen ...` or
35
+ * `bun ts/db-rls/src/migrate-gen.ts ...`.
36
+ *
37
+ * Returns exit code (0 on success, non-zero on usage error). Caller
38
+ * (a small bin/migrate-gen.js shim) translates to process.exit.
39
+ */
40
+ export declare function runMigrateGenCli(args: readonly string[]): number;
41
+ //# sourceMappingURL=migrate-gen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-gen.d.ts","sourceRoot":"","sources":["../src/migrate-gen.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,eAAe;IAC9B;wDACoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;6CAEyC;IACzC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAehE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAWhE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAanE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CA6BhE"}
@@ -0,0 +1,123 @@
1
+ // migrate-gen — generates the per-service residual migration phase
2
+ // per § 5.7.
3
+ //
4
+ // Output:
5
+ // <outDir>/004-helpers-and-grants/0001_user_tenant_ids.sql
6
+ //
7
+ // The generated file installs `auth.user_tenant_ids(uuid)` joining the
8
+ // service's `employees_mirror` + `customers_mirror` tables (per PII
9
+ // doctrine § Q5 + db-rls Q1 lock). Caller services apply it AFTER
10
+ // Drizzle migrations create the mirror tables (or wire the stub
11
+ // 0000_user_tenant_ids_stub.sql per the chicken-and-egg path).
12
+ //
13
+ // CLI shape:
14
+ // npx @nodii/db-rls migrate-gen <serviceName> [--out <path>]
15
+ // bun ts/db-rls/src/migrate-gen.ts <serviceName> [--out <path>]
16
+ //
17
+ // Output is byte-equal across TS / Python / Go ports — parity-fence test
18
+ // asserts directly.
19
+ import { mkdirSync, writeFileSync } from "node:fs";
20
+ import { dirname, join, resolve } from "node:path";
21
+ /**
22
+ * SQL text for the real (joins mirror tables) helper. Module-level
23
+ * constant so parity-fence can byte-compare.
24
+ */
25
+ export function userTenantIdsRealSql(serviceName) {
26
+ return `-- Service: ${serviceName}
27
+ -- Generated by @nodii/db-rls migrate-gen
28
+ -- Phase: 004-helpers-and-grants — service-owned auth.user_tenant_ids
29
+ -- Spec: § 5.7 (terraform-bootstrap split)
30
+
31
+ CREATE OR REPLACE FUNCTION auth.user_tenant_ids(p_user_id uuid) RETURNS SETOF uuid
32
+ LANGUAGE sql STABLE AS $$
33
+ SELECT tenant_id FROM employees_mirror WHERE id = p_user_id
34
+ UNION
35
+ SELECT tenant_id FROM customers_mirror WHERE id = p_user_id
36
+ $$;
37
+
38
+ GRANT EXECUTE ON FUNCTION auth.user_tenant_ids(uuid) TO nodii_services, authenticated;
39
+ `;
40
+ }
41
+ /**
42
+ * SQL text for the chicken-and-egg stub variant (returns empty set).
43
+ */
44
+ export function userTenantIdsStubSql(serviceName) {
45
+ return `-- Service: ${serviceName}
46
+ -- Generated by @nodii/db-rls migrate-gen --stub
47
+ -- Phase: 004-helpers-and-grants — stub variant (returns empty set)
48
+ -- Spec: § 5.7 (chicken-and-egg)
49
+
50
+ CREATE OR REPLACE FUNCTION auth.user_tenant_ids(p_user_id uuid) RETURNS SETOF uuid
51
+ LANGUAGE sql STABLE AS $$ SELECT NULL::uuid WHERE false $$;
52
+
53
+ GRANT EXECUTE ON FUNCTION auth.user_tenant_ids(uuid) TO nodii_services, authenticated;
54
+ `;
55
+ }
56
+ /**
57
+ * Generates the residual migration phase file and returns its contents.
58
+ * If `outDir` is set, writes the file to disk; otherwise just returns
59
+ * the contents (used by tests + parity-fence).
60
+ */
61
+ export function migrateGen(input) {
62
+ const contents = input.stub
63
+ ? userTenantIdsStubSql(input.serviceName)
64
+ : userTenantIdsRealSql(input.serviceName);
65
+ const fileName = input.stub
66
+ ? "0000_user_tenant_ids_stub.sql"
67
+ : "0001_user_tenant_ids.sql";
68
+ const phase = "004-helpers-and-grants";
69
+ const outDir = resolve(input.outDir ?? "./infra/postgres");
70
+ const path = join(outDir, phase, fileName);
71
+ mkdirSync(dirname(path), { recursive: true });
72
+ writeFileSync(path, contents);
73
+ return { path, contents };
74
+ }
75
+ /**
76
+ * CLI entrypoint. Invoked via `npx @nodii/db-rls migrate-gen ...` or
77
+ * `bun ts/db-rls/src/migrate-gen.ts ...`.
78
+ *
79
+ * Returns exit code (0 on success, non-zero on usage error). Caller
80
+ * (a small bin/migrate-gen.js shim) translates to process.exit.
81
+ */
82
+ export function runMigrateGenCli(args) {
83
+ const positional = [];
84
+ let outDir;
85
+ let stub = false;
86
+ for (let i = 0; i < args.length; i++) {
87
+ const a = args[i];
88
+ if (a === "--out" && i + 1 < args.length) {
89
+ outDir = args[++i];
90
+ }
91
+ else if (a === "--stub") {
92
+ stub = true;
93
+ }
94
+ else if (a === "--help" || a === "-h") {
95
+ printUsage();
96
+ return 0;
97
+ }
98
+ else if (a && !a.startsWith("--")) {
99
+ positional.push(a);
100
+ }
101
+ }
102
+ // CLI form: `migrate-gen <serviceName>` — the first positional after the
103
+ // optional "migrate-gen" leading token (when invoked via `npx`).
104
+ const serviceName = positional[0] === "migrate-gen" ? positional[1] : positional[0];
105
+ if (!serviceName) {
106
+ printUsage();
107
+ return 2;
108
+ }
109
+ const result = migrateGen({ serviceName, outDir, stub });
110
+ // eslint-disable-next-line no-console
111
+ console.log(result.path);
112
+ return 0;
113
+ }
114
+ function printUsage() {
115
+ // eslint-disable-next-line no-console
116
+ console.log(`usage: migrate-gen <serviceName> [--out <dir>] [--stub]
117
+
118
+ Emits infra/postgres/004-helpers-and-grants/0001_user_tenant_ids.sql
119
+ (or the stub variant 0000_user_tenant_ids_stub.sql with --stub).
120
+
121
+ Spec: @nodii/db-rls § 5.7 (terraform-bootstrap split).`);
122
+ }
123
+ //# sourceMappingURL=migrate-gen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-gen.js","sourceRoot":"","sources":["../src/migrate-gen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import type { Pool } from "pg";
2
+ import type { PgPool, PgTx, PoolKind } from "./types";
3
+ export interface PgPoolAdapterOpts {
4
+ /** Underlying pg.Pool. Caller owns lifecycle (creation + .end()). */
5
+ pool: Pool;
6
+ /** Logical pool role — drives error/metric labels. */
7
+ kind: PoolKind;
8
+ /** Acquire-time threshold (ms) above which we surface a slow-acquire
9
+ * telemetry hook. Defaults to 50ms per § 5.10. */
10
+ slowAcquireMs?: number;
11
+ /** Optional hook fired when acquisition exceeds `slowAcquireMs`. Tests
12
+ * hook this to assert the metric path fires. */
13
+ onSlowAcquire?: (info: {
14
+ pool: PoolKind;
15
+ durationMs: number;
16
+ }) => void;
17
+ /** Optional hook fired every time a tx finishes (commit OR rollback).
18
+ * Tests use this to assert lifecycle parity. */
19
+ onTxFinish?: (info: {
20
+ pool: PoolKind;
21
+ outcome: "committed" | "rolled_back" | "error";
22
+ durationMs: number;
23
+ }) => void;
24
+ }
25
+ /**
26
+ * Real PgPool adapter wrapping `pg.Pool`. Caller supplies the underlying
27
+ * pool with the right role credentials (boot validation in
28
+ * `configureDbRls` enforces this).
29
+ */
30
+ export declare class PgClientPool implements PgPool {
31
+ readonly kind: PoolKind;
32
+ private readonly pool;
33
+ private readonly slowAcquireMs;
34
+ private readonly onSlowAcquire?;
35
+ private readonly onTxFinish?;
36
+ constructor(opts: PgPoolAdapterOpts);
37
+ /** Direct access to the underlying pg.Pool (for ORM clients that need
38
+ * the raw handle — Drizzle's `drizzle(pool)` constructor for instance).
39
+ * Library code does NOT use this for tx work; that path goes through
40
+ * withTransaction so the GUC + release lifecycle is enforced. */
41
+ get rawPool(): Pool;
42
+ withTransaction<T>(fn: (tx: PgTx) => Promise<T>): Promise<T>;
43
+ }
44
+ //# sourceMappingURL=pg-pool-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-pool-adapter.d.ts","sourceRoot":"","sources":["../src/pg-pool-adapter.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,IAAI,EAAE,IAAI,CAAC;IACX,sDAAsD;IACtD,IAAI,EAAE,QAAQ,CAAC;IACf;uDACmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;qDACiD;IACjD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACvE;qDACiD;IACjD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,IAAI,EAAE,QAAQ,CAAC;QACf,OAAO,EAAE,WAAW,GAAG,aAAa,GAAG,OAAO,CAAC;QAC/C,UAAU,EAAE,MAAM,CAAC;KACpB,KAAK,IAAI,CAAC;CACZ;AAsBD;;;;GAIG;AACH,qBAAa,YAAa,YAAW,MAAM;IACzC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAqC;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAkC;gBAElD,IAAI,EAAE,iBAAiB;IAQnC;;;sEAGkE;IAClE,IAAI,OAAO,IAAI,IAAI,CAElB;IAEK,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAgDnE"}
@@ -0,0 +1,111 @@
1
+ // Real PgPool adapter wrapping a `pg.Pool` instance — implements the
2
+ // PgPool/PgTx surface from src/types.ts so the existing with*Context
3
+ // helpers, in-memory test doubles, and parity-fence asserts all keep
4
+ // working unchanged.
5
+ //
6
+ // Lifecycle per § 5.3:
7
+ // 1. Acquire client from pg.Pool (timed; metric emitted on slow acquire)
8
+ // 2. BEGIN
9
+ // 3. set_config(GUC, value, true) ← caller-driven via PgTx.setConfig
10
+ // 4. yield client to caller
11
+ // 5. COMMIT (or ROLLBACK on throw) + release
12
+ //
13
+ // Pool-leak guard: client.release() ALWAYS runs in the finally block,
14
+ // regardless of commit/rollback outcome. The acquire-timer fires a
15
+ // telemetry span when acquisition exceeds 50ms (§ 5.10).
16
+ class PgClientTx {
17
+ poolKind;
18
+ client;
19
+ constructor(kind, client) {
20
+ this.poolKind = kind;
21
+ this.client = client;
22
+ }
23
+ async exec(sql, params = []) {
24
+ await this.client.query(sql, params);
25
+ }
26
+ async setConfig(key, value) {
27
+ // set_config(name, value, is_local=true) — tx-local per § 5.3.
28
+ // Never expose the false variant; library refuses session-scoped GUCs.
29
+ await this.client.query("SELECT set_config($1, $2, true)", [key, value]);
30
+ }
31
+ }
32
+ /**
33
+ * Real PgPool adapter wrapping `pg.Pool`. Caller supplies the underlying
34
+ * pool with the right role credentials (boot validation in
35
+ * `configureDbRls` enforces this).
36
+ */
37
+ export class PgClientPool {
38
+ kind;
39
+ pool;
40
+ slowAcquireMs;
41
+ onSlowAcquire;
42
+ onTxFinish;
43
+ constructor(opts) {
44
+ this.kind = opts.kind;
45
+ this.pool = opts.pool;
46
+ this.slowAcquireMs = opts.slowAcquireMs ?? 50;
47
+ this.onSlowAcquire = opts.onSlowAcquire;
48
+ this.onTxFinish = opts.onTxFinish;
49
+ }
50
+ /** Direct access to the underlying pg.Pool (for ORM clients that need
51
+ * the raw handle — Drizzle's `drizzle(pool)` constructor for instance).
52
+ * Library code does NOT use this for tx work; that path goes through
53
+ * withTransaction so the GUC + release lifecycle is enforced. */
54
+ get rawPool() {
55
+ return this.pool;
56
+ }
57
+ async withTransaction(fn) {
58
+ const acquireStart = Date.now();
59
+ const client = await this.pool.connect();
60
+ const acquireMs = Date.now() - acquireStart;
61
+ if (acquireMs >= this.slowAcquireMs && this.onSlowAcquire) {
62
+ try {
63
+ this.onSlowAcquire({ pool: this.kind, durationMs: acquireMs });
64
+ }
65
+ catch {
66
+ /* hook errors are non-fatal */
67
+ }
68
+ }
69
+ const txStart = Date.now();
70
+ let outcome = "error";
71
+ try {
72
+ await client.query("BEGIN");
73
+ const tx = new PgClientTx(this.kind, client);
74
+ let result;
75
+ try {
76
+ result = await fn(tx);
77
+ }
78
+ catch (err) {
79
+ // Rollback then re-throw. Swallow rollback failure (don't double-throw).
80
+ try {
81
+ await client.query("ROLLBACK");
82
+ }
83
+ catch {
84
+ /* swallow per § 5.9 */
85
+ }
86
+ outcome = "rolled_back";
87
+ throw err;
88
+ }
89
+ await client.query("COMMIT");
90
+ outcome = "committed";
91
+ return result;
92
+ }
93
+ finally {
94
+ const durationMs = Date.now() - txStart;
95
+ try {
96
+ this.onTxFinish?.({ pool: this.kind, outcome, durationMs });
97
+ }
98
+ catch {
99
+ /* hook errors are non-fatal */
100
+ }
101
+ // Pool-leak guard — release ALWAYS runs, even on commit/rollback failure.
102
+ try {
103
+ client.release();
104
+ }
105
+ catch {
106
+ /* swallow release errors */
107
+ }
108
+ }
109
+ }
110
+ }
111
+ //# sourceMappingURL=pg-pool-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-pool-adapter.js","sourceRoot":"","sources":["../src/pg-pool-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export type RlsVerb = "SELECT" | "INSERT" | "UPDATE" | "DELETE";
2
+ export interface WithRlsOpts {
3
+ /** Defaults to `["SELECT", "INSERT"]` (the audit-flavor floor). */
4
+ allow?: readonly RlsVerb[];
5
+ /** Tenant column name; defaults to "tenant_id". */
6
+ tenantColumn?: string;
7
+ }
8
+ /**
9
+ * Emits the policy DDL block for `table`.
10
+ *
11
+ * Returned string ends with `\n` for file-friendliness. Single
12
+ * deterministic output (no whitespace drift, stable ordering) so the
13
+ * parity-fence test is byte-equal.
14
+ *
15
+ * If `table` is schema-qualified (`schema.tbl`), the ALTER + ON clauses
16
+ * use the qualified form; the policy NAME uses only the unqualified
17
+ * tail (Postgres policy names live in the table's namespace and must
18
+ * be a plain identifier, not a dotted path).
19
+ */
20
+ export declare function withRls(table: string, opts?: WithRlsOpts): string;
21
+ /**
22
+ * Per-table grants — non-owner roles need GRANT on the verbs in `allow`.
23
+ * Owner doesn't need a grant (BYPASSRLS + table-owner).
24
+ */
25
+ export declare function generateTableGrants(table: string, opts?: WithRlsOpts): string;
26
+ //# sourceMappingURL=rls-policy-gen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls-policy-gen.d.ts","sourceRoot":"","sources":["../src/rls-policy-gen.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B,mEAAmE;IACnE,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC3B,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,MAAM,CA0BrE;AAQD;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,WAAgB,GACrB,MAAM,CASR"}