@stratum-hq/db-adapters 1.1.0 → 1.3.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
@@ -56,6 +56,16 @@ try {
56
56
  }
57
57
  ```
58
58
 
59
+ `createPolicy` checks every row-level security policy already on the table (the table the name resolves to through the `search_path`) before it adds the `tenant_isolation` policy or keeps an existing one, and throws without changing anything if any check fails:
60
+
61
+ - PostgreSQL ORs permissive policies together, so every permissive policy, whatever its name or roles, must compare `tenant_id` with the current tenant setting `app.current_tenant_id` for the commands it covers (`USING` for reads, updates and deletes; `WITH CHECK` for inserts, and for updates when set). A role-specific permissive policy counts too, because `createPolicy` cannot know which role your application connects as.
62
+ - Restrictive policies can only narrow access, so they may check anything.
63
+ - An existing `tenant_isolation` policy must be permissive, apply to all commands, and apply to `PUBLIC` (no `TO` clause), like the one `createPolicy` generates and every policy Stratum ships.
64
+
65
+ The check recognizes the form Stratum generates, with the operands in either order, with casts, ANDed with other conditions, or ORed with Stratum's `app.bypass_rls` bypass. A policy that isolates correctly but is written in another form is also refused; replace it with the generated form. This is a breaking change for callers that relied on the old skip, shipped in a minor release.
66
+
67
+ `isRLSEnabled` reports on the table that the name resolves to through the `search_path`, not on a table with the same name in another schema.
68
+
59
69
  Also available: `disableRLS`, `dropPolicy`, `isRLSEnabled`, `addTenantColumn`, `createIsolationPolicy`, and low-level session helpers `setTenantContext` / `resetTenantContext` / `getCurrentTenantId`. Schema-per-tenant and database-per-tenant variants (`SchemaRawAdapter`, `DatabasePoolManager`, …) are exported too.
60
70
 
61
71
  ## Schema-per-tenant search_path
@@ -76,6 +86,23 @@ Extra schemas come **after** the tenant schema, and each entry must be a plain i
76
86
 
77
87
  Prisma qualifies every table with its datasource schema, so `search_path` does not route it. For Prisma, use `new SchemaPrismaAdapter(PrismaClient, datasourceUrl).getClient(tenantSlug)`, which gives each tenant a client bound to its own schema.
78
88
 
89
+ ## Database-per-tenant pools
90
+
91
+ `DatabasePoolManager` keeps one `pg.Pool` for each tenant database. Each `getPool(slug)` call holds the pool until you call `releasePool(slug)` with the same arguments. The manager never ends a held pool. Thus a request that is using a pool cannot lose it to eviction.
92
+
93
+ ```typescript
94
+ const pool = await poolManager.getPool("acme");
95
+ try {
96
+ await pool.query("SELECT 1");
97
+ } finally {
98
+ poolManager.releasePool("acme");
99
+ }
100
+ ```
101
+
102
+ When the pool count reaches `maxPools`, the manager ends the least recently used pool that no caller holds. If every pool is held, the count goes above `maxPools` until a caller releases one. A pool that you never release stays open until `closePool` or `closeAll`. `DatabaseRawAdapter` releases its pool after each call.
103
+
104
+ Concurrent first requests for one tenant share one pool.
105
+
79
106
  ## Security
80
107
 
81
108
  - All DDL validates table names against `/^[a-zA-Z_][a-zA-Z0-9_]*$/`.
@@ -5,6 +5,8 @@ import { DatabasePoolManager } from "../database/pool-manager.js";
5
5
  *
6
6
  * Each query is routed to the dedicated database for the given tenant slug
7
7
  * by obtaining a connection from the tenant's pool via DatabasePoolManager.
8
+ * Each method releases its hold on the pool when it finishes, so the manager
9
+ * can evict the pool while no query uses it.
8
10
  */
9
11
  export declare class DatabaseRawAdapter {
10
12
  private readonly poolManager;
@@ -1 +1 @@
1
- {"version":3,"file":"database-raw.d.ts","sourceRoot":"","sources":["../../src/adapters/database-raw.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;GAKG;AACH,qBAAa,kBAAkB;IACjB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,mBAAmB;IAE7D;;;OAGG;IACG,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,cAAc,EACzD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAU7B;;;OAGG;IACG,wBAAwB,CAAC,CAAC,EAC9B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7C,OAAO,CAAC,CAAC,CAAC;CAed"}
1
+ {"version":3,"file":"database-raw.d.ts","sourceRoot":"","sources":["../../src/adapters/database-raw.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IACjB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,mBAAmB;IAE7D;;;OAGG;IACG,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,cAAc,EACzD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAc7B;;;OAGG;IACG,wBAAwB,CAAC,CAAC,EAC9B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7C,OAAO,CAAC,CAAC,CAAC;CA6Bd"}
@@ -6,6 +6,8 @@ exports.DatabaseRawAdapter = void 0;
6
6
  *
7
7
  * Each query is routed to the dedicated database for the given tenant slug
8
8
  * by obtaining a connection from the tenant's pool via DatabasePoolManager.
9
+ * Each method releases its hold on the pool when it finishes, so the manager
10
+ * can evict the pool while no query uses it.
9
11
  */
10
12
  class DatabaseRawAdapter {
11
13
  poolManager;
@@ -18,12 +20,17 @@ class DatabaseRawAdapter {
18
20
  */
19
21
  async query(tenantSlug, text, values) {
20
22
  const pool = await this.poolManager.getPool(tenantSlug);
21
- const client = await pool.connect();
22
23
  try {
23
- return await client.query(text, values);
24
+ const client = await pool.connect();
25
+ try {
26
+ return await client.query(text, values);
27
+ }
28
+ finally {
29
+ client.release();
30
+ }
24
31
  }
25
32
  finally {
26
- client.release();
33
+ this.poolManager.releasePool(tenantSlug);
27
34
  }
28
35
  }
29
36
  /**
@@ -32,19 +39,35 @@ class DatabaseRawAdapter {
32
39
  */
33
40
  async executeWithTenantContext(tenantSlug, queryFn) {
34
41
  const pool = await this.poolManager.getPool(tenantSlug);
35
- const client = await pool.connect();
36
42
  try {
37
- await client.query("BEGIN");
38
- const result = await queryFn(client);
39
- await client.query("COMMIT");
40
- return result;
41
- }
42
- catch (err) {
43
- await client.query("ROLLBACK");
44
- throw err;
43
+ const client = await pool.connect();
44
+ // A failed ROLLBACK must not replace the caller's error. That error goes
45
+ // to client.release instead: a truthy argument makes pg-pool destroy the
46
+ // connection, so no later caller gets a connection in an unknown
47
+ // transaction state.
48
+ let releaseErr;
49
+ try {
50
+ await client.query("BEGIN");
51
+ const result = await queryFn(client);
52
+ await client.query("COMMIT");
53
+ return result;
54
+ }
55
+ catch (err) {
56
+ try {
57
+ await client.query("ROLLBACK");
58
+ }
59
+ catch (rollbackErr) {
60
+ releaseErr =
61
+ rollbackErr instanceof Error ? rollbackErr : new Error(String(rollbackErr));
62
+ }
63
+ throw err;
64
+ }
65
+ finally {
66
+ client.release(releaseErr);
67
+ }
45
68
  }
46
69
  finally {
47
- client.release();
70
+ this.poolManager.releasePool(tenantSlug);
48
71
  }
49
72
  }
50
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"database-raw.js","sourceRoot":"","sources":["../../src/adapters/database-raw.ts"],"names":[],"mappings":";;;AAGA;;;;;GAKG;AACH,MAAa,kBAAkB;IACA;IAA7B,YAA6B,WAAgC;QAAhC,gBAAW,GAAX,WAAW,CAAqB;IAAG,CAAC;IAEjE;;;OAGG;IACH,KAAK,CAAC,KAAK,CACT,UAAkB,EAClB,IAAY,EACZ,MAAkB;QAElB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,KAAK,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,wBAAwB,CAC5B,UAAkB,EAClB,OAA8C;QAE9C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/B,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;CACF;AA3CD,gDA2CC"}
1
+ {"version":3,"file":"database-raw.js","sourceRoot":"","sources":["../../src/adapters/database-raw.ts"],"names":[],"mappings":";;;AAGA;;;;;;;GAOG;AACH,MAAa,kBAAkB;IACA;IAA7B,YAA6B,WAAgC;QAAhC,gBAAW,GAAX,WAAW,CAAqB;IAAG,CAAC;IAEjE;;;OAGG;IACH,KAAK,CAAC,KAAK,CACT,UAAkB,EAClB,IAAY,EACZ,MAAkB;QAElB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,IAAI,CAAC;gBACH,OAAO,MAAM,MAAM,CAAC,KAAK,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;YAC7C,CAAC;oBAAS,CAAC;gBACT,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,wBAAwB,CAC5B,UAAkB,EAClB,OAA8C;QAE9C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,yEAAyE;YACzE,yEAAyE;YACzE,iEAAiE;YACjE,qBAAqB;YACrB,IAAI,UAA6B,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC5B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;gBACrC,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC7B,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBACjC,CAAC;gBAAC,OAAO,WAAW,EAAE,CAAC;oBACrB,UAAU;wBACR,WAAW,YAAY,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;gBAChF,CAAC;gBACD,MAAM,GAAG,CAAC;YACZ,CAAC;oBAAS,CAAC;gBACT,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;CACF;AA7DD,gDA6DC"}
@@ -1 +1 @@
1
- {"version":3,"file":"schema-raw.d.ts","sourceRoot":"","sources":["../../src/adapters/schema-raw.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAKpB,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,qBAAa,gBAAgB;IAIzB,OAAO,CAAC,IAAI;IAHd,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAW;gBAGjC,IAAI,EAAE,EAAE,CAAC,IAAI,EACrB,OAAO,GAAE,oBAAyB;IAM9B,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,cAAc,EACzD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAM7B;;;;OAIG;IACG,wBAAwB,CAAC,CAAC,EAC9B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACxC,OAAO,CAAC,CAAC,CAAC;CAqBd;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,SAAS,EAAE,MAAM,MAAM,EACvB,OAAO,GAAE,oBAAyB,GACjC,gBAAgB,CAWlB"}
1
+ {"version":3,"file":"schema-raw.d.ts","sourceRoot":"","sources":["../../src/adapters/schema-raw.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAKpB,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAMD,qBAAa,gBAAgB;IAIzB,OAAO,CAAC,IAAI;IAHd,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAW;gBAGjC,IAAI,EAAE,EAAE,CAAC,IAAI,EACrB,OAAO,GAAE,oBAAyB;IAM9B,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,cAAc,EACzD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAM7B;;;;OAIG;IACG,wBAAwB,CAAC,CAAC,EAC9B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACxC,OAAO,CAAC,CAAC,CAAC;CAkCd;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,SAAS,EAAE,MAAM,MAAM,EACvB,OAAO,GAAE,oBAAyB,GACjC,gBAAgB,CAWlB"}
@@ -5,6 +5,9 @@ exports.createSchemaTenantPool = createSchemaTenantPool;
5
5
  const core_1 = require("@stratum-hq/core");
6
6
  const manager_js_1 = require("../schema/manager.js");
7
7
  const session_js_1 = require("../schema/session.js");
8
+ function toError(err) {
9
+ return err instanceof Error ? err : new Error(String(err));
10
+ }
8
11
  class SchemaRawAdapter {
9
12
  pool;
10
13
  extraSearchPath;
@@ -27,6 +30,11 @@ class SchemaRawAdapter {
27
30
  const safe = (0, core_1.validateSlug)(tenantSlug);
28
31
  const schemaName = (0, manager_js_1.tenantSchemaName)(safe);
29
32
  const client = await this.pool.connect();
33
+ // A failed ROLLBACK or RESET must not replace the result or the caller's
34
+ // error. That error goes to client.release instead: a truthy argument makes
35
+ // pg-pool destroy the connection, so no later caller gets a connection in
36
+ // an unknown transaction state or with an unknown search_path.
37
+ let releaseErr;
30
38
  try {
31
39
  await client.query("BEGIN");
32
40
  // SET LOCAL is transaction-scoped; schemaName is derived from a validated slug.
@@ -36,12 +44,22 @@ class SchemaRawAdapter {
36
44
  return result;
37
45
  }
38
46
  catch (err) {
39
- await client.query("ROLLBACK");
47
+ try {
48
+ await client.query("ROLLBACK");
49
+ }
50
+ catch (rollbackErr) {
51
+ releaseErr = toError(rollbackErr);
52
+ }
40
53
  throw err;
41
54
  }
42
55
  finally {
43
- await (0, session_js_1.resetSearchPath)(client);
44
- client.release();
56
+ try {
57
+ await (0, session_js_1.resetSearchPath)(client);
58
+ }
59
+ catch (resetErr) {
60
+ releaseErr ??= toError(resetErr);
61
+ }
62
+ client.release(releaseErr);
45
63
  }
46
64
  }
47
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"schema-raw.js","sourceRoot":"","sources":["../../src/adapters/schema-raw.ts"],"names":[],"mappings":";;;AAmEA,wDAeC;AAjFD,2CAAgD;AAChD,qDAA4E;AAC5E,qDAAyE;AAYzE,MAAa,gBAAgB;IAIjB;IAHO,eAAe,CAAW;IAE3C,YACU,IAAa,EACrB,UAAgC,EAAE;QAD1B,SAAI,GAAJ,IAAI,CAAS;QAGrB,yEAAyE;QACzE,IAAI,CAAC,eAAe,GAAG,CAAC,OAAO,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,+BAAkB,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,KAAK,CACT,UAAkB,EAClB,IAAY,EACZ,MAAkB;QAElB,OAAO,IAAI,CAAC,wBAAwB,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAChE,OAAO,MAAM,CAAC,KAAK,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,wBAAwB,CAC5B,UAAkB,EAClB,EAAyC;QAEzC,MAAM,IAAI,GAAG,IAAA,mBAAY,EAAC,UAAU,CAAC,CAAC;QACtC,MAAM,UAAU,GAAG,IAAA,6BAAgB,EAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5B,gFAAgF;YAChF,MAAM,MAAM,CAAC,KAAK,CAChB,4BAA4B,IAAA,6BAAgB,EAAC,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,EAAE,CACjF,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;YAChC,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/B,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,MAAM,IAAA,4BAAe,EAAC,MAAM,CAAC,CAAC;YAC9B,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;CACF;AAlDD,4CAkDC;AAED,SAAgB,sBAAsB,CACpC,IAAa,EACb,SAAuB,EACvB,UAAgC,EAAE;IAElC,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpD,OAAO,IAAI,KAAK,CAAC,OAAO,EAAE;QACxB,GAAG,CAAC,MAAM,EAAE,IAAI;YACd,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAY,EAAE,MAAkB,EAAE,EAAE,CAC1C,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC;YACD,OAAQ,MAAsD,CAAC,IAAI,CAAC,CAAC;QACvE,CAAC;KACF,CAAqB,CAAC;AACzB,CAAC"}
1
+ {"version":3,"file":"schema-raw.js","sourceRoot":"","sources":["../../src/adapters/schema-raw.ts"],"names":[],"mappings":";;;AAoFA,wDAeC;AAlGD,2CAAgD;AAChD,qDAA4E;AAC5E,qDAAyE;AAYzE,SAAS,OAAO,CAAC,GAAY;IAC3B,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,MAAa,gBAAgB;IAIjB;IAHO,eAAe,CAAW;IAE3C,YACU,IAAa,EACrB,UAAgC,EAAE;QAD1B,SAAI,GAAJ,IAAI,CAAS;QAGrB,yEAAyE;QACzE,IAAI,CAAC,eAAe,GAAG,CAAC,OAAO,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,+BAAkB,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,KAAK,CACT,UAAkB,EAClB,IAAY,EACZ,MAAkB;QAElB,OAAO,IAAI,CAAC,wBAAwB,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAChE,OAAO,MAAM,CAAC,KAAK,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,wBAAwB,CAC5B,UAAkB,EAClB,EAAyC;QAEzC,MAAM,IAAI,GAAG,IAAA,mBAAY,EAAC,UAAU,CAAC,CAAC;QACtC,MAAM,UAAU,GAAG,IAAA,6BAAgB,EAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACzC,yEAAyE;QACzE,4EAA4E;QAC5E,0EAA0E;QAC1E,+DAA+D;QAC/D,IAAI,UAA6B,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5B,gFAAgF;YAChF,MAAM,MAAM,CAAC,KAAK,CAChB,4BAA4B,IAAA,6BAAgB,EAAC,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,EAAE,CACjF,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;YAChC,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YACjC,CAAC;YAAC,OAAO,WAAW,EAAE,CAAC;gBACrB,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;YACpC,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC;gBACH,MAAM,IAAA,4BAAe,EAAC,MAAM,CAAC,CAAC;YAChC,CAAC;YAAC,OAAO,QAAQ,EAAE,CAAC;gBAClB,UAAU,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;YACnC,CAAC;YACD,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;CACF;AA/DD,4CA+DC;AAED,SAAgB,sBAAsB,CACpC,IAAa,EACb,SAAuB,EACvB,UAAgC,EAAE;IAElC,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpD,OAAO,IAAI,KAAK,CAAC,OAAO,EAAE;QACxB,GAAG,CAAC,MAAM,EAAE,IAAI;YACd,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAY,EAAE,MAAkB,EAAE,EAAE,CAC1C,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC;YACD,OAAQ,MAAsD,CAAC,IAAI,CAAC,CAAC;QACvE,CAAC;KACF,CAAqB,CAAC;AACzB,CAAC"}
@@ -4,40 +4,71 @@ export interface DatabasePoolManagerOptions {
4
4
  baseConnectionConfig: pg.PoolConfig;
5
5
  /** Maximum number of tenant pools to keep open simultaneously. Default: 50. */
6
6
  maxPools?: number;
7
- /** Milliseconds of inactivity before a pool is eligible for LRU eviction. Default: 30000. */
7
+ /**
8
+ * Milliseconds an idle connection stays open inside a tenant pool before pg
9
+ * closes it. The manager passes it to pg.Pool as idleTimeoutMillis. It does
10
+ * not control LRU eviction. Default: 30000.
11
+ */
8
12
  idleTimeoutMs?: number;
9
13
  }
10
14
  /**
11
15
  * Manages a collection of per-tenant pg.Pool instances.
12
16
  *
13
- * Pools are keyed by tenant slug and created on first access.
14
- * When the pool count exceeds maxPools, the least-recently-used pool is evicted.
17
+ * Pools are keyed by tenant slug and created on first access. Each getPool call
18
+ * holds the pool until the caller calls releasePool. When the pool count reaches
19
+ * maxPools, the least-recently-used pool that no caller holds is evicted. A pool
20
+ * that a caller holds is never evicted, so the count can exceed maxPools while
21
+ * every pool is in use.
15
22
  */
16
23
  export declare class DatabasePoolManager {
17
24
  private readonly pools;
25
+ /** Holds that callers still have on pools that closePool or closeAll removed, by pool key. */
26
+ private readonly closedHolds;
18
27
  private readonly baseConfig;
19
28
  private readonly maxPools;
20
29
  private readonly idleTimeoutMs;
21
30
  constructor(options: DatabasePoolManagerOptions);
22
31
  /**
23
- * Returns an existing pool for the tenant slug, or creates a new one.
24
- * Evicts the LRU pool if the pool limit is reached.
32
+ * Returns the pool for the tenant slug, and creates it on first access.
33
+ * Each call holds the pool until the caller calls releasePool with the same
34
+ * arguments. A held pool is never evicted.
25
35
  *
26
36
  * When a regionId is provided, the pool is keyed as `regionId:slug` to support
27
37
  * multi-region deployments where the same slug may exist in different regions.
28
38
  */
29
39
  getPool(tenantSlug: string, regionId?: string): Promise<pg.Pool>;
40
+ /**
41
+ * Ends one hold on the pool that getPool returned for the same arguments.
42
+ * Call it once for each getPool call. No-op if the pool is not tracked.
43
+ *
44
+ * Without a regionId, a bare slug also matches one region-prefixed pool
45
+ * (`regionId:slug`), as closePool does. If the slug matches pools in more
46
+ * than one region, the call releases nothing, because the manager cannot
47
+ * tell which pool the caller holds. Pass the regionId in that case.
48
+ */
49
+ releasePool(tenantSlug: string, regionId?: string): void;
30
50
  /** Closes and removes the pool for the given tenant slug. No-op if not found.
31
51
  * Accepts either a bare slug or a region-prefixed key (`regionId:slug`). */
32
52
  closePool(tenantSlug: string, regionId?: string): Promise<void>;
33
53
  /** Closes all managed pools. Call during application shutdown. */
34
54
  closeAll(): Promise<void>;
55
+ /** Keeps the holds of a removed pool, so that their releases do not reach a later pool for the key. */
56
+ private recordClosedHolds;
57
+ /**
58
+ * Returns the key that releasePool acts on: the exact key when the manager
59
+ * tracks it, else the one region-prefixed key for a bare slug. Returns
60
+ * undefined when no key or more than one key matches.
61
+ */
62
+ private resolveHeldKey;
35
63
  /** Returns a snapshot of current pool statistics. */
36
64
  getStats(): {
37
65
  poolCount: number;
38
66
  activeConnections: number;
39
67
  };
40
- /** Evicts the pool that has been idle the longest. */
41
- private evictLRU;
68
+ /**
69
+ * Removes the least-recently-used pool that no caller holds from the map and
70
+ * returns it. The caller ends it. Returns undefined when every pool is held.
71
+ */
72
+ private takeLRU;
42
73
  }
43
74
  //# sourceMappingURL=pool-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pool-manager.d.ts","sourceRoot":"","sources":["../../src/database/pool-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpB,MAAM,WAAW,0BAA0B;IACzC,mHAAmH;IACnH,oBAAoB,EAAE,EAAE,CAAC,UAAU,CAAC;IACpC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAOD;;;;;GAKG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,OAAO,EAAE,0BAA0B;IAM/C;;;;;;OAMG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC;IAwBtE;gFAC4E;IACtE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBrE,kEAAkE;IAC5D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B,qDAAqD;IACrD,QAAQ,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE;IAW5D,sDAAsD;YACxC,QAAQ;CAevB"}
1
+ {"version":3,"file":"pool-manager.d.ts","sourceRoot":"","sources":["../../src/database/pool-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpB,MAAM,WAAW,0BAA0B;IACzC,mHAAmH;IACnH,oBAAoB,EAAE,EAAE,CAAC,UAAU,CAAC;IACpC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AASD;;;;;;;;GAQG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,8FAA8F;IAC9F,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkC;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,OAAO,EAAE,0BAA0B;IAM/C;;;;;;;OAOG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC;IAgCtE;;;;;;;;OAQG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAqBxD;gFAC4E;IACtE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBrE,kEAAkE;IAC5D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAO/B,uGAAuG;IACvG,OAAO,CAAC,iBAAiB;IAKzB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAWtB,qDAAqD;IACrD,QAAQ,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE;IAW5D;;;OAGG;IACH,OAAO,CAAC,OAAO;CAgBhB"}
@@ -9,11 +9,16 @@ const manager_js_1 = require("./manager.js");
9
9
  /**
10
10
  * Manages a collection of per-tenant pg.Pool instances.
11
11
  *
12
- * Pools are keyed by tenant slug and created on first access.
13
- * When the pool count exceeds maxPools, the least-recently-used pool is evicted.
12
+ * Pools are keyed by tenant slug and created on first access. Each getPool call
13
+ * holds the pool until the caller calls releasePool. When the pool count reaches
14
+ * maxPools, the least-recently-used pool that no caller holds is evicted. A pool
15
+ * that a caller holds is never evicted, so the count can exceed maxPools while
16
+ * every pool is in use.
14
17
  */
15
18
  class DatabasePoolManager {
16
19
  pools = new Map();
20
+ /** Holds that callers still have on pools that closePool or closeAll removed, by pool key. */
21
+ closedHolds = new Map();
17
22
  baseConfig;
18
23
  maxPools;
19
24
  idleTimeoutMs;
@@ -23,8 +28,9 @@ class DatabasePoolManager {
23
28
  this.idleTimeoutMs = options.idleTimeoutMs ?? 30_000;
24
29
  }
25
30
  /**
26
- * Returns an existing pool for the tenant slug, or creates a new one.
27
- * Evicts the LRU pool if the pool limit is reached.
31
+ * Returns the pool for the tenant slug, and creates it on first access.
32
+ * Each call holds the pool until the caller calls releasePool with the same
33
+ * arguments. A held pool is never evicted.
28
34
  *
29
35
  * When a regionId is provided, the pool is keyed as `regionId:slug` to support
30
36
  * multi-region deployments where the same slug may exist in different regions.
@@ -32,22 +38,63 @@ class DatabasePoolManager {
32
38
  async getPool(tenantSlug, regionId) {
33
39
  const dbName = (0, manager_js_1.getDatabaseName)(tenantSlug);
34
40
  const poolKey = regionId ? `${regionId}:${tenantSlug}` : tenantSlug;
35
- const existing = this.pools.get(poolKey);
36
- if (existing) {
37
- existing.lastUsed = Date.now();
38
- return existing.pool;
41
+ // The lookup, the eviction choice and the insert run with no await between
42
+ // them. Thus a concurrent call for the same key always finds this entry.
43
+ let entry = this.pools.get(poolKey);
44
+ let victim;
45
+ if (!entry) {
46
+ if (this.pools.size >= this.maxPools)
47
+ victim = this.takeLRU();
48
+ entry = {
49
+ pool: new pg_1.default.Pool({
50
+ ...this.baseConfig,
51
+ database: dbName,
52
+ idleTimeoutMillis: this.idleTimeoutMs,
53
+ }),
54
+ lastUsed: Date.now(),
55
+ refCount: 0,
56
+ };
57
+ this.pools.set(poolKey, entry);
39
58
  }
40
- // Evict before adding so we never exceed maxPools.
41
- if (this.pools.size >= this.maxPools) {
42
- await this.evictLRU();
59
+ entry.lastUsed = Date.now();
60
+ entry.refCount++;
61
+ if (victim) {
62
+ // The evicted pool belongs to a different tenant. Its end error must not
63
+ // fail this request, which already has a usable pool.
64
+ await victim.end().catch(() => undefined);
43
65
  }
44
- const pool = new pg_1.default.Pool({
45
- ...this.baseConfig,
46
- database: dbName,
47
- idleTimeoutMillis: this.idleTimeoutMs,
48
- });
49
- this.pools.set(poolKey, { pool, lastUsed: Date.now() });
50
- return pool;
66
+ return entry.pool;
67
+ }
68
+ /**
69
+ * Ends one hold on the pool that getPool returned for the same arguments.
70
+ * Call it once for each getPool call. No-op if the pool is not tracked.
71
+ *
72
+ * Without a regionId, a bare slug also matches one region-prefixed pool
73
+ * (`regionId:slug`), as closePool does. If the slug matches pools in more
74
+ * than one region, the call releases nothing, because the manager cannot
75
+ * tell which pool the caller holds. Pass the regionId in that case.
76
+ */
77
+ releasePool(tenantSlug, regionId) {
78
+ const poolKey = this.resolveHeldKey(tenantSlug, regionId);
79
+ if (poolKey === undefined)
80
+ return;
81
+ // The caller passes a key, not the pool, so the manager cannot tell a
82
+ // hold on a closed pool from a hold on its replacement. It ends holds on
83
+ // closed pools first. The replacement then counts as held for longer than
84
+ // it is, which is safe: eviction never ends a pool that is in use.
85
+ const closed = this.closedHolds.get(poolKey);
86
+ if (closed !== undefined) {
87
+ if (closed === 1)
88
+ this.closedHolds.delete(poolKey);
89
+ else
90
+ this.closedHolds.set(poolKey, closed - 1);
91
+ return;
92
+ }
93
+ const entry = this.pools.get(poolKey);
94
+ if (!entry || entry.refCount === 0)
95
+ return;
96
+ entry.refCount--;
97
+ entry.lastUsed = Date.now();
51
98
  }
52
99
  /** Closes and removes the pool for the given tenant slug. No-op if not found.
53
100
  * Accepts either a bare slug or a region-prefixed key (`regionId:slug`). */
@@ -68,14 +115,39 @@ class DatabasePoolManager {
68
115
  if (!entry)
69
116
  return;
70
117
  this.pools.delete(resolvedKey);
118
+ this.recordClosedHolds(resolvedKey, entry);
71
119
  await entry.pool.end();
72
120
  }
73
121
  /** Closes all managed pools. Call during application shutdown. */
74
122
  async closeAll() {
75
123
  const entries = Array.from(this.pools.entries());
76
124
  this.pools.clear();
125
+ for (const [key, entry] of entries)
126
+ this.recordClosedHolds(key, entry);
77
127
  await Promise.all(entries.map(([, entry]) => entry.pool.end()));
78
128
  }
129
+ /** Keeps the holds of a removed pool, so that their releases do not reach a later pool for the key. */
130
+ recordClosedHolds(poolKey, entry) {
131
+ if (entry.refCount === 0)
132
+ return;
133
+ this.closedHolds.set(poolKey, (this.closedHolds.get(poolKey) ?? 0) + entry.refCount);
134
+ }
135
+ /**
136
+ * Returns the key that releasePool acts on: the exact key when the manager
137
+ * tracks it, else the one region-prefixed key for a bare slug. Returns
138
+ * undefined when no key or more than one key matches.
139
+ */
140
+ resolveHeldKey(tenantSlug, regionId) {
141
+ const poolKey = regionId ? `${regionId}:${tenantSlug}` : tenantSlug;
142
+ if (this.pools.has(poolKey) || this.closedHolds.has(poolKey) || regionId)
143
+ return poolKey;
144
+ const matches = new Set();
145
+ for (const key of [...this.pools.keys(), ...this.closedHolds.keys()]) {
146
+ if (key.endsWith(`:${tenantSlug}`))
147
+ matches.add(key);
148
+ }
149
+ return matches.size === 1 ? [...matches][0] : undefined;
150
+ }
79
151
  /** Returns a snapshot of current pool statistics. */
80
152
  getStats() {
81
153
  let activeConnections = 0;
@@ -87,19 +159,24 @@ class DatabasePoolManager {
87
159
  }
88
160
  return { poolCount: this.pools.size, activeConnections };
89
161
  }
90
- /** Evicts the pool that has been idle the longest. */
91
- async evictLRU() {
162
+ /**
163
+ * Removes the least-recently-used pool that no caller holds from the map and
164
+ * returns it. The caller ends it. Returns undefined when every pool is held.
165
+ */
166
+ takeLRU() {
92
167
  let oldestKey;
93
168
  let oldestTime = Infinity;
94
169
  for (const [key, entry] of this.pools.entries()) {
95
- if (entry.lastUsed < oldestTime) {
170
+ if (entry.refCount === 0 && entry.lastUsed < oldestTime) {
96
171
  oldestTime = entry.lastUsed;
97
172
  oldestKey = key;
98
173
  }
99
174
  }
100
- if (oldestKey !== undefined) {
101
- await this.closePool(oldestKey);
102
- }
175
+ if (oldestKey === undefined)
176
+ return undefined;
177
+ const entry = this.pools.get(oldestKey);
178
+ this.pools.delete(oldestKey);
179
+ return entry.pool;
103
180
  }
104
181
  }
105
182
  exports.DatabasePoolManager = DatabasePoolManager;
@@ -1 +1 @@
1
- {"version":3,"file":"pool-manager.js","sourceRoot":"","sources":["../../src/database/pool-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pool-manager.js","sourceRoot":"","sources":["../../src/database/pool-manager.ts"],"names":[],"mappings":"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"}
@@ -23,7 +23,7 @@ async function enableRLS(client, tableName) {
23
23
  async function createIsolationPolicy(client, tableName) {
24
24
  const safe = validateTableName(tableName);
25
25
  await client.query(`CREATE POLICY tenant_isolation ON ${safe}
26
- USING (tenant_id = current_setting('app.current_tenant_id')::uuid)`);
26
+ USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid)`);
27
27
  }
28
28
  async function migrateTable(client, tableName) {
29
29
  await addTenantColumn(client, tableName);
@@ -1 +1 @@
1
- {"version":3,"file":"migration-helpers.js","sourceRoot":"","sources":["../src/migration-helpers.ts"],"names":[],"mappings":";;AAUA,0CAQC;AAED,8BAOC;AAED,sDASC;AAED,oCAOC;AA7CD,wFAAwF;AACxF,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAEM,KAAK,UAAU,eAAe,CACnC,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAChB,eAAe,IAAI,qCAAqC,CACzD,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,SAAS,CAC7B,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,4BAA4B,CAAC,CAAC;IACpE,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,2BAA2B,CAAC,CAAC;AACrE,CAAC;AAEM,KAAK,UAAU,qBAAqB,CACzC,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAChB,qCAAqC,IAAI;wEAC2B,CACrE,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,SAAiB;IAEjB,MAAM,eAAe,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACzC,MAAM,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACnC,MAAM,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AACjD,CAAC"}
1
+ {"version":3,"file":"migration-helpers.js","sourceRoot":"","sources":["../src/migration-helpers.ts"],"names":[],"mappings":";;AAUA,0CAQC;AAED,8BAOC;AAED,sDASC;AAED,oCAOC;AA7CD,wFAAwF;AACxF,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAEM,KAAK,UAAU,eAAe,CACnC,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAChB,eAAe,IAAI,qCAAqC,CACzD,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,SAAS,CAC7B,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,4BAA4B,CAAC,CAAC;IACpE,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,2BAA2B,CAAC,CAAC;AACrE,CAAC;AAEM,KAAK,UAAU,qBAAqB,CACzC,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAChB,qCAAqC,IAAI;0FAC6C,CACvF,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,SAAiB;IAEjB,MAAM,eAAe,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACzC,MAAM,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACnC,MAAM,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AACjD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/rls/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAUpB,wBAAsB,YAAY,CAChC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,SAAS,CAC7B,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CASlB"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/rls/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAapB,wBAAsB,YAAY,CAChC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,SAAS,CAC7B,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAWlB"}
@@ -5,6 +5,8 @@ exports.dropPolicy = dropPolicy;
5
5
  exports.enableRLS = enableRLS;
6
6
  exports.disableRLS = disableRLS;
7
7
  exports.isRLSEnabled = isRLSEnabled;
8
+ const policy_check_js_1 = require("./policy-check.js");
9
+ const TENANT_FILTER = "tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid";
8
10
  // Validate table name to prevent SQL injection (only allows alphanumeric + underscores)
9
11
  function validateTableName(tableName) {
10
12
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
@@ -14,11 +16,24 @@ function validateTableName(tableName) {
14
16
  }
15
17
  async function createPolicy(client, tableName) {
16
18
  const safe = validateTableName(tableName);
17
- // Cannot use parameterized queries inside DO blocks or for DDL identifiers.
18
- // Table name is validated via allowlist regex above.
19
- const exists = await client.query(`SELECT count(*) FROM pg_policies WHERE tablename = $1 AND policyname = 'tenant_isolation'`, [safe]);
20
- if (parseInt(exists.rows[0].count, 10) === 0) {
21
- await client.query(`CREATE POLICY tenant_isolation ON ${safe} USING (tenant_id = current_setting('app.current_tenant_id')::uuid)`);
19
+ // Read every policy on the table that the name resolves to, in whichever
20
+ // schema that is. PostgreSQL ORs permissive policies together, so each one
21
+ // must filter by tenant, and a policy's name proves nothing.
22
+ const existing = await client.query(`SELECT p.policyname, p.permissive, p.cmd, p.qual, p.with_check, p.roles::text[] AS roles
23
+ FROM pg_policies p
24
+ JOIN pg_class c ON c.relname = p.tablename
25
+ JOIN pg_namespace n ON n.oid = c.relnamespace AND n.nspname = p.schemaname
26
+ WHERE c.oid = to_regclass($1)`, [safe]);
27
+ const issues = (0, policy_check_js_1.tablePolicyIssues)(existing.rows);
28
+ if (issues.length > 0) {
29
+ throw new Error(`[stratum] Table ${safe} has row-level security policies that do not isolate it by tenant: ` +
30
+ `${issues.join("; ")} (expected ${TENANT_FILTER}). ` +
31
+ `Drop or correct those policies, then call createPolicy again.`);
32
+ }
33
+ if (!existing.rows.some((p) => p.policyname === "tenant_isolation")) {
34
+ // Cannot use parameterized queries inside DO blocks or for DDL identifiers.
35
+ // Table name is validated via allowlist regex above.
36
+ await client.query(`CREATE POLICY tenant_isolation ON ${safe} USING (${TENANT_FILTER})`);
22
37
  }
23
38
  }
24
39
  async function dropPolicy(client, tableName) {
@@ -36,7 +51,9 @@ async function disableRLS(client, tableName) {
36
51
  await client.query(`ALTER TABLE ${safe} DISABLE ROW LEVEL SECURITY`);
37
52
  }
38
53
  async function isRLSEnabled(client, tableName) {
39
- const res = await client.query(`SELECT relrowsecurity FROM pg_class WHERE relname = $1`, [tableName]);
54
+ const safe = validateTableName(tableName);
55
+ // The table the name resolves to, in whichever schema that is.
56
+ const res = await client.query(`SELECT relrowsecurity FROM pg_class WHERE oid = to_regclass($1)`, [safe]);
40
57
  if (res.rows.length === 0) {
41
58
  return false;
42
59
  }
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/rls/manager.ts"],"names":[],"mappings":";;AAUA,oCAgBC;AAED,gCAMC;AAED,8BAOC;AAED,gCAOC;AAED,oCAYC;AAhED,wFAAwF;AACxF,SAAS,iBAAiB,CAAC,SAAiB;IAC1C,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,uBAAuB,SAAS,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,4EAA4E;IAC5E,qDAAqD;IACrD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,2FAA2F,EAC3F,CAAC,IAAI,CAAC,CACP,CAAC;IACF,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,MAAM,CAAC,KAAK,CAChB,qCAAqC,IAAI,qEAAqE,CAC/G,CAAC;IACJ,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,UAAU,CAC9B,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAAC,6CAA6C,IAAI,EAAE,CAAC,CAAC;AAC1E,CAAC;AAEM,KAAK,UAAU,SAAS,CAC7B,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,4BAA4B,CAAC,CAAC;IACpE,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,2BAA2B,CAAC,CAAC;AACrE,CAAC;AAEM,KAAK,UAAU,UAAU,CAC9B,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,8BAA8B,CAAC,CAAC;IACtE,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,6BAA6B,CAAC,CAAC;AACvE,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,SAAiB;IAEjB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,wDAAwD,EACxD,CAAC,SAAS,CAAC,CACZ,CAAC;IACF,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;AACpC,CAAC"}
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/rls/manager.ts"],"names":[],"mappings":";;AAaA,oCA6BC;AAED,gCAMC;AAED,8BAOC;AAED,gCAOC;AAED,oCAcC;AAnFD,uDAAsE;AAEtE,MAAM,aAAa,GAAG,8EAA8E,CAAC;AAErG,wFAAwF;AACxF,SAAS,iBAAiB,CAAC,SAAiB;IAC1C,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,uBAAuB,SAAS,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,yEAAyE;IACzE,2EAA2E;IAC3E,6DAA6D;IAC7D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CACjC;;;;oCAIgC,EAChC,CAAC,IAAI,CAAC,CACP,CAAC;IACF,MAAM,MAAM,GAAG,IAAA,mCAAiB,EAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAChD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,mBAAmB,IAAI,qEAAqE;YAC1F,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,aAAa,KAAK;YACpD,+DAA+D,CAClE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,kBAAkB,CAAC,EAAE,CAAC;QACpE,4EAA4E;QAC5E,qDAAqD;QACrD,MAAM,MAAM,CAAC,KAAK,CAAC,qCAAqC,IAAI,WAAW,aAAa,GAAG,CAAC,CAAC;IAC3F,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,UAAU,CAC9B,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAAC,6CAA6C,IAAI,EAAE,CAAC,CAAC;AAC1E,CAAC;AAEM,KAAK,UAAU,SAAS,CAC7B,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,4BAA4B,CAAC,CAAC;IACpE,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,2BAA2B,CAAC,CAAC;AACrE,CAAC;AAEM,KAAK,UAAU,UAAU,CAC9B,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,8BAA8B,CAAC,CAAC;IACtE,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,6BAA6B,CAAC,CAAC;AACvE,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,SAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC1C,+DAA+D;IAC/D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,iEAAiE,EACjE,CAAC,IAAI,CAAC,CACP,CAAC;IACF,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;AACpC,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Decides whether the row-level security policies on a table restrict rows to
3
+ * the current tenant, from the expressions PostgreSQL stores in pg_policies.
4
+ *
5
+ * `createPolicy` uses this before it keeps an existing tenant_isolation policy
6
+ * or adds one, because a policy's name proves nothing about what it checks, and
7
+ * PostgreSQL ORs permissive policies together: one permissive policy that does
8
+ * not filter by tenant opens the table whatever tenant_isolation says.
9
+ *
10
+ * Rules:
11
+ * - Every permissive policy must filter by tenant for the commands it covers:
12
+ * USING for SELECT, UPDATE, DELETE and ALL, WITH CHECK for INSERT, and WITH
13
+ * CHECK for UPDATE and ALL when it is set. This applies whatever roles the
14
+ * policy names, because this package cannot know which role the
15
+ * application connects as.
16
+ * - Restrictive policies can only narrow access, so they may check anything.
17
+ * - The tenant_isolation policy itself must be permissive, apply to ALL
18
+ * commands, and apply to PUBLIC, like the one `createPolicy` generates and
19
+ * every policy Stratum ships.
20
+ *
21
+ * The check recognises the form this package generates, with the operands in
22
+ * either order, with casts, inside an AND with other conditions, and ORed with
23
+ * Stratum's administrative bypass. Anything else is reported as not filtering
24
+ * by tenant, so an unusual but correct policy is refused, never accepted.
25
+ */
26
+ /** One row of pg_policies. */
27
+ export interface PolicyRow {
28
+ policyname: string;
29
+ /** "PERMISSIVE" or "RESTRICTIVE". */
30
+ permissive: string;
31
+ /** "ALL", "SELECT", "INSERT", "UPDATE" or "DELETE". */
32
+ cmd: string;
33
+ qual: string | null;
34
+ with_check: string | null;
35
+ /** The roles the policy applies to; ["public"] for PUBLIC. */
36
+ roles: string[];
37
+ }
38
+ /**
39
+ * Every reason the table's policies do not isolate it by tenant. Empty when
40
+ * they do, or when the table has no policies.
41
+ */
42
+ export declare function tablePolicyIssues(policies: PolicyRow[]): string[];
43
+ //# sourceMappingURL=policy-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-check.d.ts","sourceRoot":"","sources":["../../src/rls/policy-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,8BAA8B;AAC9B,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,8DAA8D;IAC9D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AA0JD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAgBjE"}
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ /**
3
+ * Decides whether the row-level security policies on a table restrict rows to
4
+ * the current tenant, from the expressions PostgreSQL stores in pg_policies.
5
+ *
6
+ * `createPolicy` uses this before it keeps an existing tenant_isolation policy
7
+ * or adds one, because a policy's name proves nothing about what it checks, and
8
+ * PostgreSQL ORs permissive policies together: one permissive policy that does
9
+ * not filter by tenant opens the table whatever tenant_isolation says.
10
+ *
11
+ * Rules:
12
+ * - Every permissive policy must filter by tenant for the commands it covers:
13
+ * USING for SELECT, UPDATE, DELETE and ALL, WITH CHECK for INSERT, and WITH
14
+ * CHECK for UPDATE and ALL when it is set. This applies whatever roles the
15
+ * policy names, because this package cannot know which role the
16
+ * application connects as.
17
+ * - Restrictive policies can only narrow access, so they may check anything.
18
+ * - The tenant_isolation policy itself must be permissive, apply to ALL
19
+ * commands, and apply to PUBLIC, like the one `createPolicy` generates and
20
+ * every policy Stratum ships.
21
+ *
22
+ * The check recognises the form this package generates, with the operands in
23
+ * either order, with casts, inside an AND with other conditions, and ORed with
24
+ * Stratum's administrative bypass. Anything else is reported as not filtering
25
+ * by tenant, so an unusual but correct policy is refused, never accepted.
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.tablePolicyIssues = tablePolicyIssues;
29
+ /** The session setting that holds the current tenant (see rls/session.ts). */
30
+ const TENANT_SETTING = "app.current_tenant_id";
31
+ /** The setting Stratum's own policies admit for administrative access. */
32
+ const BYPASS_SETTING = "app.bypass_rls";
33
+ /** Drops whitespace and casts, lower-cases, and keeps string literals intact. */
34
+ function normalize(expr) {
35
+ let out = "";
36
+ for (let i = 0; i < expr.length; i++) {
37
+ const ch = expr[i];
38
+ if (ch === "'") {
39
+ const end = expr.indexOf("'", i + 1);
40
+ const stop = end === -1 ? expr.length : end + 1;
41
+ out += expr.slice(i, stop);
42
+ i = stop - 1;
43
+ }
44
+ else if (!/\s/.test(ch)) {
45
+ out += ch.toLowerCase();
46
+ }
47
+ }
48
+ // Casts such as ::text, ::uuid and ::character varying (spaces already gone).
49
+ return out.replace(/::[a-z_]+(?:\[\])?/g, "");
50
+ }
51
+ /** Index of the parenthesis that closes the one at `open`. */
52
+ function closingParen(expr, open) {
53
+ let depth = 0;
54
+ for (let i = open; i < expr.length; i++) {
55
+ const ch = expr[i];
56
+ if (ch === "'") {
57
+ const end = expr.indexOf("'", i + 1);
58
+ if (end === -1)
59
+ return -1;
60
+ i = end;
61
+ }
62
+ else if (ch === "(") {
63
+ depth++;
64
+ }
65
+ else if (ch === ")") {
66
+ depth--;
67
+ if (depth === 0)
68
+ return i;
69
+ }
70
+ }
71
+ return -1;
72
+ }
73
+ /** Removes parentheses that wrap the whole expression. */
74
+ function stripOuterParens(expr) {
75
+ let e = expr;
76
+ while (e.startsWith("(") && e.endsWith(")") && closingParen(e, 0) === e.length - 1) {
77
+ e = e.slice(1, -1);
78
+ }
79
+ return e;
80
+ }
81
+ /**
82
+ * Splits a normalized expression on a top-level boolean operator. Whitespace
83
+ * is gone, so the operator is found between a closing parenthesis and an
84
+ * opening one, which is how PostgreSQL prints every operand of AND / OR.
85
+ */
86
+ function splitTopLevel(expr, op) {
87
+ const parts = [];
88
+ let depth = 0;
89
+ let start = 0;
90
+ for (let i = 0; i < expr.length; i++) {
91
+ const ch = expr[i];
92
+ if (ch === "'") {
93
+ const end = expr.indexOf("'", i + 1);
94
+ if (end === -1)
95
+ break;
96
+ i = end;
97
+ }
98
+ else if (ch === "(") {
99
+ depth++;
100
+ }
101
+ else if (ch === ")") {
102
+ depth--;
103
+ if (depth === 0 && expr.startsWith(`${op}(`, i + 1)) {
104
+ parts.push(expr.slice(start, i + 1));
105
+ start = i + 1 + op.length;
106
+ }
107
+ }
108
+ }
109
+ parts.push(expr.slice(start));
110
+ return parts.map(stripOuterParens);
111
+ }
112
+ /** Removes every parenthesis; only safe on an operand with no AND / OR in it. */
113
+ function flatten(expr) {
114
+ return expr.replace(/[()]/g, "");
115
+ }
116
+ const SETTING_READ = `current_setting'${TENANT_SETTING.replace(".", "\\.")}'(?:,(?:true|false))?`;
117
+ /** The current tenant, read directly or through NULLIF(..., ''). */
118
+ const CURRENT_TENANT = `(?:nullif${SETTING_READ},''|${SETTING_READ})`;
119
+ const TENANT_MATCH = new RegExp(`^(?:tenant_id=${CURRENT_TENANT}|${CURRENT_TENANT}=tenant_id)$`);
120
+ const BYPASS_MATCH = new RegExp(`^current_setting'${BYPASS_SETTING.replace(".", "\\.")}'(?:,(?:true|false))?='on'$`);
121
+ /** True when the normalized expression only admits rows of the current tenant. */
122
+ function filtersByTenant(expr) {
123
+ const e = stripOuterParens(expr);
124
+ const disjuncts = splitTopLevel(e, "or");
125
+ if (disjuncts.length > 1) {
126
+ // Every branch must be safe; Stratum's administrative bypass is one of them.
127
+ return (disjuncts.every((d) => BYPASS_MATCH.test(flatten(d)) || filtersByTenant(d)) &&
128
+ disjuncts.some((d) => !BYPASS_MATCH.test(flatten(d))));
129
+ }
130
+ const conjuncts = splitTopLevel(e, "and");
131
+ if (conjuncts.length > 1) {
132
+ return conjuncts.some((c) => filtersByTenant(c));
133
+ }
134
+ return TENANT_MATCH.test(flatten(e));
135
+ }
136
+ /** An expression on one line, for messages. */
137
+ function oneLine(expr) {
138
+ return expr === null ? "none" : expr.replace(/\s+/g, " ");
139
+ }
140
+ /** Why one permissive policy lets rows of other tenants through, or null. */
141
+ function permissiveIssue(p) {
142
+ const name = `policy "${oneLine(p.policyname)}" (${p.cmd})`;
143
+ // INSERT policies have only WITH CHECK; SELECT and DELETE only USING. For
144
+ // ALL and UPDATE a missing WITH CHECK means PostgreSQL reuses USING.
145
+ if (p.cmd !== "INSERT") {
146
+ if (p.qual === null || !filtersByTenant(normalize(p.qual))) {
147
+ return `${name} USING (${oneLine(p.qual)}) does not filter by tenant`;
148
+ }
149
+ }
150
+ if (p.cmd === "INSERT" || p.with_check !== null) {
151
+ if (p.with_check === null || !filtersByTenant(normalize(p.with_check))) {
152
+ return `${name} WITH CHECK (${oneLine(p.with_check)}) does not filter by tenant`;
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+ /** Why the tenant_isolation policy is not the shape createPolicy generates, or null. */
158
+ function tenantIsolationShapeIssue(p) {
159
+ const name = `policy "${oneLine(p.policyname)}"`;
160
+ if (p.permissive !== "PERMISSIVE") {
161
+ return `${name} is ${p.permissive.toLowerCase()}, not permissive`;
162
+ }
163
+ if (p.cmd !== "ALL") {
164
+ return `${name} applies to ${p.cmd} only, not ALL commands`;
165
+ }
166
+ if (p.roles.length !== 1 || p.roles[0] !== "public") {
167
+ return `${name} applies to roles ${p.roles.join(", ")}, not PUBLIC`;
168
+ }
169
+ return null;
170
+ }
171
+ /**
172
+ * Every reason the table's policies do not isolate it by tenant. Empty when
173
+ * they do, or when the table has no policies.
174
+ */
175
+ function tablePolicyIssues(policies) {
176
+ const issues = [];
177
+ for (const p of policies) {
178
+ if (p.policyname === "tenant_isolation") {
179
+ const shape = tenantIsolationShapeIssue(p);
180
+ if (shape !== null) {
181
+ issues.push(shape);
182
+ continue;
183
+ }
184
+ }
185
+ if (p.permissive === "PERMISSIVE") {
186
+ const issue = permissiveIssue(p);
187
+ if (issue !== null)
188
+ issues.push(issue);
189
+ }
190
+ }
191
+ return issues;
192
+ }
193
+ //# sourceMappingURL=policy-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-check.js","sourceRoot":"","sources":["../../src/rls/policy-check.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratum-hq/db-adapters",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Stratum PostgreSQL adapters for tenant-scoped query isolation",
5
5
  "keywords": [
6
6
  "multi-tenancy",
@@ -41,7 +41,7 @@
41
41
  "registry": "https://registry.npmjs.org/"
42
42
  },
43
43
  "dependencies": {
44
- "@stratum-hq/core": "*",
44
+ "@stratum-hq/core": "^1.4.0",
45
45
  "pg": "^8.11.0"
46
46
  },
47
47
  "peerDependencies": {