@stratum-hq/db-adapters 0.3.0 → 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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=schema-session.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-session.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/schema-session.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const session_js_1 = require("../schema/session.js");
5
+ function makeClient() {
6
+ const query = vitest_1.vi.fn(async () => ({ rows: [], rowCount: 0 }));
7
+ return { query };
8
+ }
9
+ (0, vitest_1.describe)("setSchemaSearchPath", () => {
10
+ (0, vitest_1.it)("sets the search_path for a valid tenant slug", async () => {
11
+ const client = makeClient();
12
+ await (0, session_js_1.setSchemaSearchPath)(client, "acme_corp_2024");
13
+ (0, vitest_1.expect)(client.query).toHaveBeenCalledWith("SET LOCAL search_path TO tenant_acme_corp_2024, public");
14
+ });
15
+ (0, vitest_1.it)("rejects a slug outside the canonical charset before issuing any query", async () => {
16
+ const client = makeClient();
17
+ // Anything with characters outside the slug charset must never reach the
18
+ // search_path statement (the schema name is interpolated, not bound).
19
+ await (0, vitest_1.expect)((0, session_js_1.setSchemaSearchPath)(client, "bad; DROP TABLE tenants--")).rejects.toThrow(/Invalid tenant slug/);
20
+ (0, vitest_1.expect)(client.query).not.toHaveBeenCalled();
21
+ });
22
+ });
23
+ //# sourceMappingURL=schema-session.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-session.test.js","sourceRoot":"","sources":["../../src/__tests__/schema-session.test.ts"],"names":[],"mappings":";;AAAA,mCAAkD;AAClD,qDAA2D;AAG3D,SAAS,UAAU;IACjB,MAAM,KAAK,GAAG,WAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7D,OAAO,EAAE,KAAK,EAA8B,CAAC;AAC/C,CAAC;AAED,IAAA,iBAAQ,EAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,IAAA,WAAE,EAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,IAAA,gCAAmB,EAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;QACpD,IAAA,eAAM,EAAC,MAAM,CAAC,KAAK,CAAC,CAAC,oBAAoB,CACvC,wDAAwD,CACzD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACrF,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,yEAAyE;QACzE,sEAAsE;QACtE,MAAM,IAAA,eAAM,EACV,IAAA,gCAAmB,EAAC,MAAM,EAAE,2BAA2B,CAAC,CACzD,CAAC,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;QACzC,IAAA,eAAM,EAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export { PrismaAdapter, withTenant } from "./adapters/prisma.js";
4
4
  export { SchemaRawAdapter, createSchemaTenantPool, } from "./adapters/schema-raw.js";
5
5
  export { SchemaPrismaAdapter, withSchemaTenant, } from "./adapters/schema-prisma.js";
6
6
  export { createPolicy, dropPolicy, enableRLS, disableRLS, isRLSEnabled, } from "./rls/manager.js";
7
- export { setTenantContext, resetTenantContext, getCurrentTenantId, } from "./rls/session.js";
7
+ export { setTenantContext, resetTenantContext, withTenantContext, withRlsBypass, getCurrentTenantId, } from "./rls/session.js";
8
8
  export { createSchema, dropSchema, schemaExists, listTenantSchemas, replicateTableToSchema, tenantSchemaName, } from "./schema/manager.js";
9
9
  export { setSchemaSearchPath, resetSearchPath, getCurrentSearchPath, } from "./schema/session.js";
10
10
  export { addTenantColumn, enableRLS as enableRLSMigration, createIsolationPolicy, migrateTable, } from "./migration-helpers.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EACL,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EACL,YAAY,EACZ,UAAU,EACV,SAAS,EACT,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,eAAe,EACf,SAAS,IAAI,kBAAkB,EAC/B,qBAAqB,EACrB,YAAY,GACb,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,eAAe,EACf,cAAc,EACd,cAAc,EACd,YAAY,GACb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAE7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EACL,cAAc,EACd,UAAU,IAAI,iBAAiB,EAC/B,eAAe,IAAI,sBAAsB,GAC1C,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EACL,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EACL,YAAY,EACZ,UAAU,EACV,SAAS,EACT,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,eAAe,EACf,SAAS,IAAI,kBAAkB,EAC/B,qBAAqB,EACrB,YAAY,GACb,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,eAAe,EACf,cAAc,EACd,cAAc,EACd,YAAY,GACb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAE7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EACL,cAAc,EACd,UAAU,IAAI,iBAAiB,EAC/B,eAAe,IAAI,sBAAsB,GAC1C,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.withDrizzleTenantScope = exports.withDrizzleTenant = exports.DrizzleAdapter = exports.withTenantScope = exports.SequelizeAdapter = exports.DatabasePrismaAdapter = exports.DatabaseRawAdapter = exports.DatabasePoolManager = exports.dropDatabase = exports.createDatabase = exports.databaseExists = exports.getDatabaseName = exports.migrateTable = exports.createIsolationPolicy = exports.enableRLSMigration = exports.addTenantColumn = exports.getCurrentSearchPath = exports.resetSearchPath = exports.setSchemaSearchPath = exports.tenantSchemaName = exports.replicateTableToSchema = exports.listTenantSchemas = exports.schemaExists = exports.dropSchema = exports.createSchema = exports.getCurrentTenantId = exports.resetTenantContext = exports.setTenantContext = exports.isRLSEnabled = exports.disableRLS = exports.enableRLS = exports.dropPolicy = exports.createPolicy = exports.withSchemaTenant = exports.SchemaPrismaAdapter = exports.createSchemaTenantPool = exports.SchemaRawAdapter = exports.withTenant = exports.PrismaAdapter = exports.createTenantPool = exports.RawAdapter = exports.BaseAdapter = void 0;
3
+ exports.withDrizzleTenantScope = exports.withDrizzleTenant = exports.DrizzleAdapter = exports.withTenantScope = exports.SequelizeAdapter = exports.DatabasePrismaAdapter = exports.DatabaseRawAdapter = exports.DatabasePoolManager = exports.dropDatabase = exports.createDatabase = exports.databaseExists = exports.getDatabaseName = exports.migrateTable = exports.createIsolationPolicy = exports.enableRLSMigration = exports.addTenantColumn = exports.getCurrentSearchPath = exports.resetSearchPath = exports.setSchemaSearchPath = exports.tenantSchemaName = exports.replicateTableToSchema = exports.listTenantSchemas = exports.schemaExists = exports.dropSchema = exports.createSchema = exports.getCurrentTenantId = exports.withRlsBypass = exports.withTenantContext = exports.resetTenantContext = exports.setTenantContext = exports.isRLSEnabled = exports.disableRLS = exports.enableRLS = exports.dropPolicy = exports.createPolicy = exports.withSchemaTenant = exports.SchemaPrismaAdapter = exports.createSchemaTenantPool = exports.SchemaRawAdapter = exports.withTenant = exports.PrismaAdapter = exports.createTenantPool = exports.RawAdapter = exports.BaseAdapter = void 0;
4
4
  var base_adapter_js_1 = require("./base-adapter.js");
5
5
  Object.defineProperty(exports, "BaseAdapter", { enumerable: true, get: function () { return base_adapter_js_1.BaseAdapter; } });
6
6
  var raw_js_1 = require("./adapters/raw.js");
@@ -24,6 +24,8 @@ Object.defineProperty(exports, "isRLSEnabled", { enumerable: true, get: function
24
24
  var session_js_1 = require("./rls/session.js");
25
25
  Object.defineProperty(exports, "setTenantContext", { enumerable: true, get: function () { return session_js_1.setTenantContext; } });
26
26
  Object.defineProperty(exports, "resetTenantContext", { enumerable: true, get: function () { return session_js_1.resetTenantContext; } });
27
+ Object.defineProperty(exports, "withTenantContext", { enumerable: true, get: function () { return session_js_1.withTenantContext; } });
28
+ Object.defineProperty(exports, "withRlsBypass", { enumerable: true, get: function () { return session_js_1.withRlsBypass; } });
27
29
  Object.defineProperty(exports, "getCurrentTenantId", { enumerable: true, get: function () { return session_js_1.getCurrentTenantId; } });
28
30
  var manager_js_2 = require("./schema/manager.js");
29
31
  Object.defineProperty(exports, "createSchema", { enumerable: true, get: function () { return manager_js_2.createSchema; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qDAAgD;AAAvC,8GAAA,WAAW,OAAA;AAEpB,4CAAiE;AAAxD,oGAAA,UAAU,OAAA;AAAE,0GAAA,gBAAgB,OAAA;AACrC,kDAAiE;AAAxD,0GAAA,aAAa,OAAA;AAAE,uGAAA,UAAU,OAAA;AAClC,0DAGkC;AAFhC,iHAAA,gBAAgB,OAAA;AAChB,uHAAA,sBAAsB,OAAA;AAExB,gEAGqC;AAFnC,uHAAA,mBAAmB,OAAA;AACnB,oHAAA,gBAAgB,OAAA;AAGlB,+CAM0B;AALxB,0GAAA,YAAY,OAAA;AACZ,wGAAA,UAAU,OAAA;AACV,uGAAA,SAAS,OAAA;AACT,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AAGd,+CAI0B;AAHxB,8GAAA,gBAAgB,OAAA;AAChB,gHAAA,kBAAkB,OAAA;AAClB,gHAAA,kBAAkB,OAAA;AAGpB,kDAO6B;AAN3B,0GAAA,YAAY,OAAA;AACZ,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AACZ,+GAAA,iBAAiB,OAAA;AACjB,oHAAA,sBAAsB,OAAA;AACtB,8GAAA,gBAAgB,OAAA;AAGlB,kDAI6B;AAH3B,iHAAA,mBAAmB,OAAA;AACnB,6GAAA,eAAe,OAAA;AACf,kHAAA,oBAAoB,OAAA;AAGtB,+DAKgC;AAJ9B,uHAAA,eAAe,OAAA;AACf,0HAAA,SAAS,OAAsB;AAC/B,6HAAA,qBAAqB,OAAA;AACrB,oHAAA,YAAY,OAAA;AAGd,0BAA0B;AAC1B,oDAK+B;AAJ7B,6GAAA,eAAe,OAAA;AACf,4GAAA,cAAc,OAAA;AACd,4GAAA,cAAc,OAAA;AACd,0GAAA,YAAY,OAAA;AAGd,8DAAiE;AAAxD,sHAAA,mBAAmB,OAAA;AAG5B,8DAAgE;AAAvD,qHAAA,kBAAkB,OAAA;AAC3B,oEAAsE;AAA7D,2HAAA,qBAAqB,OAAA;AAC9B,wDAA4E;AAAnE,gHAAA,gBAAgB,OAAA;AAAE,+GAAA,eAAe,OAAA;AAC1C,oDAI+B;AAH7B,4GAAA,cAAc,OAAA;AACd,+GAAA,UAAU,OAAqB;AAC/B,oHAAA,eAAe,OAA0B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qDAAgD;AAAvC,8GAAA,WAAW,OAAA;AAEpB,4CAAiE;AAAxD,oGAAA,UAAU,OAAA;AAAE,0GAAA,gBAAgB,OAAA;AACrC,kDAAiE;AAAxD,0GAAA,aAAa,OAAA;AAAE,uGAAA,UAAU,OAAA;AAClC,0DAGkC;AAFhC,iHAAA,gBAAgB,OAAA;AAChB,uHAAA,sBAAsB,OAAA;AAExB,gEAGqC;AAFnC,uHAAA,mBAAmB,OAAA;AACnB,oHAAA,gBAAgB,OAAA;AAGlB,+CAM0B;AALxB,0GAAA,YAAY,OAAA;AACZ,wGAAA,UAAU,OAAA;AACV,uGAAA,SAAS,OAAA;AACT,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AAGd,+CAM0B;AALxB,8GAAA,gBAAgB,OAAA;AAChB,gHAAA,kBAAkB,OAAA;AAClB,+GAAA,iBAAiB,OAAA;AACjB,2GAAA,aAAa,OAAA;AACb,gHAAA,kBAAkB,OAAA;AAGpB,kDAO6B;AAN3B,0GAAA,YAAY,OAAA;AACZ,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AACZ,+GAAA,iBAAiB,OAAA;AACjB,oHAAA,sBAAsB,OAAA;AACtB,8GAAA,gBAAgB,OAAA;AAGlB,kDAI6B;AAH3B,iHAAA,mBAAmB,OAAA;AACnB,6GAAA,eAAe,OAAA;AACf,kHAAA,oBAAoB,OAAA;AAGtB,+DAKgC;AAJ9B,uHAAA,eAAe,OAAA;AACf,0HAAA,SAAS,OAAsB;AAC/B,6HAAA,qBAAqB,OAAA;AACrB,oHAAA,YAAY,OAAA;AAGd,0BAA0B;AAC1B,oDAK+B;AAJ7B,6GAAA,eAAe,OAAA;AACf,4GAAA,cAAc,OAAA;AACd,4GAAA,cAAc,OAAA;AACd,0GAAA,YAAY,OAAA;AAGd,8DAAiE;AAAxD,sHAAA,mBAAmB,OAAA;AAG5B,8DAAgE;AAAvD,qHAAA,kBAAkB,OAAA;AAC3B,oEAAsE;AAA7D,2HAAA,qBAAqB,OAAA;AAC9B,wDAA4E;AAAnE,gHAAA,gBAAgB,OAAA;AAAE,+GAAA,eAAe,OAAA;AAC1C,oDAI+B;AAH7B,4GAAA,cAAc,OAAA;AACd,+GAAA,UAAU,OAAqB;AAC/B,oHAAA,eAAe,OAA0B"}
@@ -2,5 +2,19 @@ import pg from "pg";
2
2
  export declare function setTenantContext(client: pg.PoolClient, tenantId: string): Promise<void>;
3
3
  export declare function resetTenantContext(client: pg.PoolClient): Promise<void>;
4
4
  export declare function withTenantContext<T>(pool: pg.Pool, tenantId: string, fn: (client: pg.PoolClient) => Promise<T>): Promise<T>;
5
+ /**
6
+ * Runs `fn` inside a transaction with the RLS bypass flag set, so control-plane
7
+ * / system operations that legitimately span tenant boundaries (provisioning,
8
+ * cascade ops, ancestry reads) can see and write all rows.
9
+ *
10
+ * The flag is set with `SET LOCAL` semantics (set_config third arg `true`), so it
11
+ * is transaction scoped and cannot leak into a later request that reuses the same
12
+ * pooled connection. This is the audited system path referenced by the RLS
13
+ * policies (which admit `current_setting('app.bypass_rls', true) = 'on'`).
14
+ *
15
+ * Use sparingly and only for operations that are known to require cross-tenant
16
+ * access. Tenant-scoped work should use `withTenantContext` instead.
17
+ */
18
+ export declare function withRlsBypass<T>(pool: pg.Pool, fn: (client: pg.PoolClient) => Promise<T>): Promise<T>;
5
19
  export declare function getCurrentTenantId(client: pg.PoolClient): Promise<string | null>;
6
20
  //# sourceMappingURL=session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/rls/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACxC,OAAO,CAAC,CAAC,CAAC,CAcZ;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,EAAE,CAAC,UAAU,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/rls/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACxC,OAAO,CAAC,CAAC,CAAC,CAcZ;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACxC,OAAO,CAAC,CAAC,CAAC,CAcZ;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,EAAE,CAAC,UAAU,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setTenantContext = setTenantContext;
4
4
  exports.resetTenantContext = resetTenantContext;
5
5
  exports.withTenantContext = withTenantContext;
6
+ exports.withRlsBypass = withRlsBypass;
6
7
  exports.getCurrentTenantId = getCurrentTenantId;
7
8
  async function setTenantContext(client, tenantId) {
8
9
  await client.query("SELECT set_config('app.current_tenant_id', $1, true)", [tenantId]);
@@ -27,6 +28,36 @@ async function withTenantContext(pool, tenantId, fn) {
27
28
  client.release();
28
29
  }
29
30
  }
31
+ /**
32
+ * Runs `fn` inside a transaction with the RLS bypass flag set, so control-plane
33
+ * / system operations that legitimately span tenant boundaries (provisioning,
34
+ * cascade ops, ancestry reads) can see and write all rows.
35
+ *
36
+ * The flag is set with `SET LOCAL` semantics (set_config third arg `true`), so it
37
+ * is transaction scoped and cannot leak into a later request that reuses the same
38
+ * pooled connection. This is the audited system path referenced by the RLS
39
+ * policies (which admit `current_setting('app.bypass_rls', true) = 'on'`).
40
+ *
41
+ * Use sparingly and only for operations that are known to require cross-tenant
42
+ * access. Tenant-scoped work should use `withTenantContext` instead.
43
+ */
44
+ async function withRlsBypass(pool, fn) {
45
+ const client = await pool.connect();
46
+ try {
47
+ await client.query("BEGIN");
48
+ await client.query("SELECT set_config('app.bypass_rls', 'on', true)");
49
+ const result = await fn(client);
50
+ await client.query("COMMIT");
51
+ return result;
52
+ }
53
+ catch (err) {
54
+ await client.query("ROLLBACK");
55
+ throw err;
56
+ }
57
+ finally {
58
+ client.release();
59
+ }
60
+ }
30
61
  async function getCurrentTenantId(client) {
31
62
  const res = await client.query(`SELECT current_setting('app.current_tenant_id', true) AS current_setting`);
32
63
  const value = res.rows[0]?.current_setting;
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/rls/session.ts"],"names":[],"mappings":";;AAEA,4CAKC;AAED,gDAEC;AAED,8CAkBC;AAED,gDAQC;AAvCM,KAAK,UAAU,gBAAgB,CACpC,MAAqB,EACrB,QAAgB;IAEhB,MAAM,MAAM,CAAC,KAAK,CAAC,sDAAsD,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzF,CAAC;AAEM,KAAK,UAAU,kBAAkB,CAAC,MAAqB;IAC5D,MAAM,MAAM,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;AAC7E,CAAC;AAEM,KAAK,UAAU,iBAAiB,CACrC,IAAa,EACb,QAAgB,EAChB,EAAyC;IAEzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,MAAM,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC/B,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,kBAAkB,CACtC,MAAqB;IAErB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,0EAA0E,CAC3E,CAAC;IACF,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC;IAC3C,OAAO,KAAK,IAAI,IAAI,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/rls/session.ts"],"names":[],"mappings":";;AAEA,4CAKC;AAED,gDAEC;AAED,8CAkBC;AAeD,sCAiBC;AAED,gDAQC;AAvEM,KAAK,UAAU,gBAAgB,CACpC,MAAqB,EACrB,QAAgB;IAEhB,MAAM,MAAM,CAAC,KAAK,CAAC,sDAAsD,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzF,CAAC;AAEM,KAAK,UAAU,kBAAkB,CAAC,MAAqB;IAC5D,MAAM,MAAM,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;AAC7E,CAAC;AAEM,KAAK,UAAU,iBAAiB,CACrC,IAAa,EACb,QAAgB,EAChB,EAAyC;IAEzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,MAAM,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC/B,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACI,KAAK,UAAU,aAAa,CACjC,IAAa,EACb,EAAyC;IAEzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,MAAM,MAAM,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC/B,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,kBAAkB,CACtC,MAAqB;IAErB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,0EAA0E,CAC3E,CAAC;IACF,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC;IAC3C,OAAO,KAAK,IAAI,IAAI,CAAC;AACvB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/schema/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpB,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1E;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,EAAE,CAAC,UAAU,GACpB,OAAO,CAAC,MAAM,CAAC,CAGjB"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/schema/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAIpB,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1E;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,EAAE,CAAC,UAAU,GACpB,OAAO,CAAC,MAAM,CAAC,CAGjB"}
@@ -3,11 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setSchemaSearchPath = setSchemaSearchPath;
4
4
  exports.resetSearchPath = resetSearchPath;
5
5
  exports.getCurrentSearchPath = getCurrentSearchPath;
6
+ const core_1 = require("@stratum-hq/core");
6
7
  const manager_js_1 = require("./manager.js");
7
8
  async function setSchemaSearchPath(client, tenantSlug) {
8
- const schemaName = (0, manager_js_1.tenantSchemaName)(tenantSlug);
9
- // Schema name is derived from a validated slug — safe for interpolation.
10
- // SET LOCAL only takes effect for the current transaction.
9
+ // The schema name is interpolated (identifiers cannot be bound), so the slug
10
+ // must be validated against the canonical slug charset first; validateSlug
11
+ // throws on anything else. SET LOCAL only takes effect for the current
12
+ // transaction.
13
+ const schemaName = (0, manager_js_1.tenantSchemaName)((0, core_1.validateSlug)(tenantSlug));
11
14
  await client.query(`SET LOCAL search_path TO ${schemaName}, public`);
12
15
  }
13
16
  async function resetSearchPath(client) {
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/schema/session.ts"],"names":[],"mappings":";;AAGA,kDAQC;AAED,0CAEC;AAED,oDAKC;AArBD,6CAAgD;AAEzC,KAAK,UAAU,mBAAmB,CACvC,MAAqB,EACrB,UAAkB;IAElB,MAAM,UAAU,GAAG,IAAA,6BAAgB,EAAC,UAAU,CAAC,CAAC;IAChD,yEAAyE;IACzE,2DAA2D;IAC3D,MAAM,MAAM,CAAC,KAAK,CAAC,4BAA4B,UAAU,UAAU,CAAC,CAAC;AACvE,CAAC;AAEM,KAAK,UAAU,eAAe,CAAC,MAAqB;IACzD,MAAM,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAC1C,CAAC;AAEM,KAAK,UAAU,oBAAoB,CACxC,MAAqB;IAErB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAA0B,kBAAkB,CAAC,CAAC;IAC5E,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;AACjC,CAAC"}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/schema/session.ts"],"names":[],"mappings":";;AAIA,kDAUC;AAED,0CAEC;AAED,oDAKC;AAxBD,2CAAgD;AAChD,6CAAgD;AAEzC,KAAK,UAAU,mBAAmB,CACvC,MAAqB,EACrB,UAAkB;IAElB,6EAA6E;IAC7E,2EAA2E;IAC3E,uEAAuE;IACvE,eAAe;IACf,MAAM,UAAU,GAAG,IAAA,6BAAgB,EAAC,IAAA,mBAAY,EAAC,UAAU,CAAC,CAAC,CAAC;IAC9D,MAAM,MAAM,CAAC,KAAK,CAAC,4BAA4B,UAAU,UAAU,CAAC,CAAC;AACvE,CAAC;AAEM,KAAK,UAAU,eAAe,CAAC,MAAqB;IACzD,MAAM,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAC1C,CAAC;AAEM,KAAK,UAAU,oBAAoB,CACxC,MAAqB;IAErB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAA0B,kBAAkB,CAAC,CAAC;IAC5E,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;AACjC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratum-hq/db-adapters",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Stratum PostgreSQL adapters for tenant-scoped query isolation",
5
5
  "keywords": [
6
6
  "multi-tenancy",
@@ -32,7 +32,8 @@
32
32
  "scripts": {
33
33
  "build": "tsc",
34
34
  "test": "vitest run",
35
- "lint": "tsc --noEmit",
35
+ "typecheck": "tsc --noEmit",
36
+ "lint": "eslint .",
36
37
  "clean": "rm -rf dist"
37
38
  },
38
39
  "publishConfig": {