@stratum-hq/db-adapters 1.0.0 → 1.1.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 (39) hide show
  1. package/README.md +22 -4
  2. package/dist/adapters/database-prisma.d.ts +4 -2
  3. package/dist/adapters/database-prisma.d.ts.map +1 -1
  4. package/dist/adapters/database-prisma.js +19 -6
  5. package/dist/adapters/database-prisma.js.map +1 -1
  6. package/dist/adapters/drizzle.d.ts +2 -2
  7. package/dist/adapters/drizzle.d.ts.map +1 -1
  8. package/dist/adapters/drizzle.js +9 -8
  9. package/dist/adapters/drizzle.js.map +1 -1
  10. package/dist/adapters/prisma.d.ts +1 -1
  11. package/dist/adapters/prisma.d.ts.map +1 -1
  12. package/dist/adapters/prisma.js +12 -5
  13. package/dist/adapters/prisma.js.map +1 -1
  14. package/dist/adapters/schema-prisma.d.ts +36 -4
  15. package/dist/adapters/schema-prisma.d.ts.map +1 -1
  16. package/dist/adapters/schema-prisma.js +55 -17
  17. package/dist/adapters/schema-prisma.js.map +1 -1
  18. package/dist/adapters/schema-raw.d.ts +14 -3
  19. package/dist/adapters/schema-raw.d.ts.map +1 -1
  20. package/dist/adapters/schema-raw.js +9 -5
  21. package/dist/adapters/schema-raw.js.map +1 -1
  22. package/dist/database/manager.d.ts.map +1 -1
  23. package/dist/database/manager.js +7 -1
  24. package/dist/database/manager.js.map +1 -1
  25. package/dist/database/pool-manager.d.ts.map +1 -1
  26. package/dist/database/pool-manager.js +2 -3
  27. package/dist/database/pool-manager.js.map +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/schema/manager.d.ts +1 -0
  32. package/dist/schema/manager.d.ts.map +1 -1
  33. package/dist/schema/manager.js +11 -2
  34. package/dist/schema/manager.js.map +1 -1
  35. package/dist/schema/session.d.ts +10 -1
  36. package/dist/schema/session.d.ts.map +1 -1
  37. package/dist/schema/session.js +24 -4
  38. package/dist/schema/session.js.map +1 -1
  39. package/package.json +3 -2
package/README.md CHANGED
@@ -31,12 +31,12 @@ Every query is wrapped in a transaction that sets `app.current_tenant_id` via a
31
31
  ## ORM Adapters
32
32
 
33
33
  ```typescript
34
- import { withTenant } from "@stratum-hq/db-adapters"; // Prisma
35
- import { withDrizzleTenant } from "@stratum-hq/db-adapters"; // Drizzle
36
- import { SequelizeAdapter, withTenantScope } from "@stratum-hq/db-adapters"; // Sequelize
34
+ import { prismaWithTenant } from "@stratum-hq/db-adapters"; // Prisma
35
+ import { drizzleWithTenant } from "@stratum-hq/db-adapters"; // Drizzle
36
+ import { SequelizeAdapter, sequelizeWithTenantScope } from "@stratum-hq/db-adapters"; // Sequelize
37
37
 
38
38
  // Prisma — all queries scoped to the current tenant
39
- const tenantPrisma = withTenant(prisma, () => getTenantContext().tenant_id, pool);
39
+ const tenantPrisma = prismaWithTenant(prisma, () => getTenantContext().tenant_id, pool);
40
40
  const orders = await tenantPrisma.order.findMany();
41
41
  ```
42
42
 
@@ -58,6 +58,24 @@ try {
58
58
 
59
59
  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
60
 
61
+ ## Schema-per-tenant search_path
62
+
63
+ `SchemaRawAdapter`, `createSchemaTenantPool` and `setSchemaSearchPath` set `search_path` to the tenant schema (`tenant_<slug>`) **alone**, inside a transaction. `public` is not on the path, so an unqualified table missing from the tenant schema is an error instead of silently reading or writing a table every tenant shares. (`setSchemaSearchPath` throws if it is called outside a transaction.)
64
+
65
+ If your queries call extension functions or types that live in another schema (for example `uuid_generate_v4()`, `ltree` operators, `citext` or `pgcrypto` installed in `public`), either schema-qualify them or opt that schema in explicitly:
66
+
67
+ ```typescript
68
+ import { SchemaRawAdapter, createSchemaTenantPool, setSchemaSearchPath } from "@stratum-hq/db-adapters";
69
+
70
+ const adapter = new SchemaRawAdapter(pool, { extraSearchPath: ["extensions"] });
71
+ const tenantPool = createSchemaTenantPool(pool, getTenantSlug, { extraSearchPath: ["extensions"] });
72
+ await setSchemaSearchPath(client, "acme", ["extensions"]); // inside BEGIN ... COMMIT
73
+ ```
74
+
75
+ Extra schemas come **after** the tenant schema, and each entry must be a plain identifier (`/^[a-zA-Z_][a-zA-Z0-9_]*$/`, validated like tenant schema names). The trade-off: an unqualified table name missing from the tenant schema resolves in the extra schemas too, so list only schemas that hold no tenant data. Prefer a dedicated extensions schema over `public`. Column defaults such as `DEFAULT uuid_generate_v4()` are bound when the table is created and work without any extra schema.
76
+
77
+ 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
+
61
79
  ## Security
62
80
 
63
81
  - All DDL validates table names against `/^[a-zA-Z_][a-zA-Z0-9_]*$/`.
@@ -28,11 +28,13 @@ export declare class DatabasePrismaAdapter {
28
28
  private readonly poolManager;
29
29
  private readonly PrismaClient;
30
30
  private readonly baseDatasourceUrl;
31
+ private readonly maxClients;
31
32
  private readonly clients;
32
- constructor(poolManager: DatabasePoolManager, PrismaClient: PrismaConstructor, baseDatasourceUrl: string);
33
+ constructor(poolManager: DatabasePoolManager, PrismaClient: PrismaConstructor, baseDatasourceUrl: string, maxClients?: number);
33
34
  /**
34
35
  * Returns a Prisma client connected to the tenant's dedicated database.
35
- * Clients are cached per tenant slug; call disconnect() to free resources.
36
+ * Clients are cached per tenant slug, up to maxClients; the least recently
37
+ * used client is disconnected when the limit is reached.
36
38
  */
37
39
  getClient(tenantSlug: string): PrismaClientLike;
38
40
  /** Disconnects and removes the cached Prisma client for the given tenant. */
@@ -1 +1 @@
1
- {"version":3,"file":"database-prisma.d.ts","sourceRoot":"","sources":["../../src/adapters/database-prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAGlE,UAAU,gBAAgB;IACxB,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,gBAAgB,CAAC;IACnD,WAAW,EAAE,CACX,KAAK,EAAE,oBAAoB,EAC3B,GAAG,MAAM,EAAE,OAAO,EAAE,KACjB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1E,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,KAAK,iBAAiB,GAAG,KAAK,OAAO,EAAE;IAAE,WAAW,EAAE;QAAE,EAAE,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,KAAK,gBAAgB,CAAC;AAErG;;;;;;;;;;GAUG;AACH,qBAAa,qBAAqB;IAI9B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IALpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4C;gBAGjD,WAAW,EAAE,mBAAmB,EAChC,YAAY,EAAE,iBAAiB,EAC/B,iBAAiB,EAAE,MAAM;IAG5C;;;OAGG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB;IAe/C,6EAA6E;IACvE,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzD,+EAA+E;IACzE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAMpC;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;CAI3B"}
1
+ {"version":3,"file":"database-prisma.d.ts","sourceRoot":"","sources":["../../src/adapters/database-prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAIlE,UAAU,gBAAgB;IACxB,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,gBAAgB,CAAC;IACnD,WAAW,EAAE,CACX,KAAK,EAAE,oBAAoB,EAC3B,GAAG,MAAM,EAAE,OAAO,EAAE,KACjB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1E,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,KAAK,iBAAiB,GAAG,KAAK,OAAO,EAAE;IAAE,WAAW,EAAE;QAAE,EAAE,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,KAAK,gBAAgB,CAAC;AAErG;;;;;;;;;;GAUG;AACH,qBAAa,qBAAqB;IAI9B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAN7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4C;gBAGjD,WAAW,EAAE,mBAAmB,EAChC,YAAY,EAAE,iBAAiB,EAC/B,iBAAiB,EAAE,MAAM,EACzB,UAAU,GAAE,MAAW;IAG1C;;;;OAIG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB;IAyB/C,6EAA6E;IACvE,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzD,+EAA+E;IACzE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAMpC;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;CAK3B"}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DatabasePrismaAdapter = void 0;
4
+ const manager_js_1 = require("../database/manager.js");
4
5
  /**
5
6
  * Prisma adapter for DB_PER_TENANT isolation.
6
7
  *
@@ -16,21 +17,32 @@ class DatabasePrismaAdapter {
16
17
  poolManager;
17
18
  PrismaClient;
18
19
  baseDatasourceUrl;
20
+ maxClients;
19
21
  clients = new Map();
20
- constructor(poolManager, PrismaClient, baseDatasourceUrl) {
22
+ constructor(poolManager, PrismaClient, baseDatasourceUrl, maxClients = 50) {
21
23
  this.poolManager = poolManager;
22
24
  this.PrismaClient = PrismaClient;
23
25
  this.baseDatasourceUrl = baseDatasourceUrl;
26
+ this.maxClients = maxClients;
24
27
  }
25
28
  /**
26
29
  * Returns a Prisma client connected to the tenant's dedicated database.
27
- * Clients are cached per tenant slug; call disconnect() to free resources.
30
+ * Clients are cached per tenant slug, up to maxClients; the least recently
31
+ * used client is disconnected when the limit is reached.
28
32
  */
29
33
  getClient(tenantSlug) {
34
+ const dbName = (0, manager_js_1.getDatabaseName)(tenantSlug);
30
35
  const existing = this.clients.get(tenantSlug);
31
- if (existing)
36
+ if (existing) {
37
+ this.clients.delete(tenantSlug);
38
+ this.clients.set(tenantSlug, existing);
32
39
  return existing;
33
- const dbName = `stratum_tenant_${tenantSlug}`;
40
+ }
41
+ if (this.clients.size >= this.maxClients) {
42
+ const [oldestKey, oldest] = this.clients.entries().next().value;
43
+ this.clients.delete(oldestKey);
44
+ void oldest.$disconnect().catch(() => { });
45
+ }
34
46
  const tenantUrl = this.buildDatasourceUrl(this.baseDatasourceUrl, dbName);
35
47
  const client = new this.PrismaClient({
36
48
  datasources: { db: { url: tenantUrl } },
@@ -58,8 +70,9 @@ class DatabasePrismaAdapter {
58
70
  * postgres://user:pass@host:port/dbname[?params]
59
71
  */
60
72
  buildDatasourceUrl(baseUrl, dbName) {
61
- // Replace the database portion of the URL (last path segment before query string).
62
- return baseUrl.replace(/\/[^/?]+(\?|$)/, `/${dbName}$1`);
73
+ const url = new URL(baseUrl);
74
+ url.pathname = `/${dbName}`;
75
+ return url.toString();
63
76
  }
64
77
  }
65
78
  exports.DatabasePrismaAdapter = DatabasePrismaAdapter;
@@ -1 +1 @@
1
- {"version":3,"file":"database-prisma.js","sourceRoot":"","sources":["../../src/adapters/database-prisma.ts"],"names":[],"mappings":";;;AAgBA;;;;;;;;;;GAUG;AACH,MAAa,qBAAqB;IAIb;IACA;IACA;IALF,OAAO,GAAkC,IAAI,GAAG,EAAE,CAAC;IAEpE,YACmB,WAAgC,EAChC,YAA+B,EAC/B,iBAAyB;QAFzB,gBAAW,GAAX,WAAW,CAAqB;QAChC,iBAAY,GAAZ,YAAY,CAAmB;QAC/B,sBAAiB,GAAjB,iBAAiB,CAAQ;IACzC,CAAC;IAEJ;;;OAGG;IACH,SAAS,CAAC,UAAkB;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAE9B,MAAM,MAAM,GAAG,kBAAkB,UAAU,EAAE,CAAC;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;QAE1E,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC;YACnC,WAAW,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACrC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,6EAA6E;IAC7E,KAAK,CAAC,gBAAgB,CAAC,UAAkB;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAChC,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;IAC7B,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,aAAa;QACjB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;IAED;;;;OAIG;IACK,kBAAkB,CAAC,OAAe,EAAE,MAAc;QACxD,mFAAmF;QACnF,OAAO,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,IAAI,MAAM,IAAI,CAAC,CAAC;IAC3D,CAAC;CACF;AApDD,sDAoDC"}
1
+ {"version":3,"file":"database-prisma.js","sourceRoot":"","sources":["../../src/adapters/database-prisma.ts"],"names":[],"mappings":";;;AACA,uDAAyD;AAgBzD;;;;;;;;;;GAUG;AACH,MAAa,qBAAqB;IAIb;IACA;IACA;IACA;IANF,OAAO,GAAkC,IAAI,GAAG,EAAE,CAAC;IAEpE,YACmB,WAAgC,EAChC,YAA+B,EAC/B,iBAAyB,EACzB,aAAqB,EAAE;QAHvB,gBAAW,GAAX,WAAW,CAAqB;QAChC,iBAAY,GAAZ,YAAY,CAAmB;QAC/B,sBAAiB,GAAjB,iBAAiB,CAAQ;QACzB,eAAU,GAAV,UAAU,CAAa;IACvC,CAAC;IAEJ;;;;OAIG;IACH,SAAS,CAAC,UAAkB;QAC1B,MAAM,MAAM,GAAG,IAAA,4BAAe,EAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YACvC,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACzC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAmC,CAAC;YAC9F,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC/B,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC5C,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;QAE1E,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC;YACnC,WAAW,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACrC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,6EAA6E;IAC7E,KAAK,CAAC,gBAAgB,CAAC,UAAkB;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAChC,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;IAC7B,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,aAAa;QACjB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;IAED;;;;OAIG;IACK,kBAAkB,CAAC,OAAe,EAAE,MAAc;QACxD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7B,GAAG,CAAC,QAAQ,GAAG,IAAI,MAAM,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;CACF;AAjED,sDAiEC"}
@@ -17,9 +17,9 @@ export declare class DrizzleAdapter extends BaseAdapter {
17
17
  * When contextFn returns an empty string the original methods are forwarded
18
18
  * without wrapping.
19
19
  */
20
- withTenant(db: DrizzleLike, contextFn: () => string): DrizzleLike;
20
+ withTenant<D extends DrizzleLike>(db: D, contextFn: () => string): Pick<D, "execute" | "transaction">;
21
21
  }
22
- export declare function withTenant(db: DrizzleLike, contextFn: () => string, pool: pg.Pool): DrizzleLike;
22
+ export declare function withTenant<D extends DrizzleLike>(db: D, contextFn: () => string, pool: pg.Pool): Pick<D, "execute" | "transaction">;
23
23
  /** @deprecated Use `withTenant` instead. */
24
24
  export declare const withTenantScope: typeof withTenant;
25
25
  //# sourceMappingURL=drizzle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../../src/adapters/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,MAAM,IAAI,CAAC;AAIpB,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACjE;AAED,qBAAa,cAAe,SAAQ,WAAW;gBACjC,IAAI,EAAE,EAAE,CAAC,IAAI;IAIzB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,MAAM,GAAG,WAAW;CA0ClE;AAGD,wBAAgB,UAAU,CACxB,EAAE,EAAE,WAAW,EACf,SAAS,EAAE,MAAM,MAAM,EACvB,IAAI,EAAE,EAAE,CAAC,IAAI,GACZ,WAAW,CAGb;AAED,4CAA4C;AAC5C,eAAO,MAAM,eAAe,mBAAa,CAAC"}
1
+ {"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../../src/adapters/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,MAAM,IAAI,CAAC;AAIpB,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACjE;AAQD,qBAAa,cAAe,SAAQ,WAAW;gBACjC,IAAI,EAAE,EAAE,CAAC,IAAI;IAIzB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,CAAC,SAAS,WAAW,EAC9B,EAAE,EAAE,CAAC,EACL,SAAS,EAAE,MAAM,MAAM,GACtB,IAAI,CAAC,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;CAsCtC;AAGD,wBAAgB,UAAU,CAAC,CAAC,SAAS,WAAW,EAC9C,EAAE,EAAE,CAAC,EACL,SAAS,EAAE,MAAM,MAAM,EACvB,IAAI,EAAE,EAAE,CAAC,IAAI,GACZ,IAAI,CAAC,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC,CAGpC;AAED,4CAA4C;AAC5C,eAAO,MAAM,eAAe,mBAAa,CAAC"}
@@ -3,6 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.withTenantScope = exports.DrizzleAdapter = void 0;
4
4
  exports.withTenant = withTenant;
5
5
  const base_adapter_js_1 = require("../base-adapter.js");
6
+ // drizzle-orm is an optional peer, so it is loaded only when this adapter runs.
7
+ async function setTenantConfig(tx, tenantId) {
8
+ const { sql } = await import("drizzle-orm");
9
+ await tx.execute(sql `SELECT set_config('app.current_tenant_id', ${tenantId}, true)`);
10
+ }
6
11
  class DrizzleAdapter extends base_adapter_js_1.BaseAdapter {
7
12
  constructor(pool) {
8
13
  super(pool);
@@ -27,10 +32,7 @@ class DrizzleAdapter extends base_adapter_js_1.BaseAdapter {
27
32
  "Use the unwrapped instance for system/admin operations.");
28
33
  }
29
34
  return original.transaction(async (tx) => {
30
- await tx.execute({
31
- sql: `SELECT set_config('app.current_tenant_id', $1, true)`,
32
- params: [tenantId],
33
- });
35
+ await setTenantConfig(tx, tenantId);
34
36
  return fn(tx);
35
37
  });
36
38
  };
@@ -41,13 +43,12 @@ class DrizzleAdapter extends base_adapter_js_1.BaseAdapter {
41
43
  "Use the unwrapped instance for system/admin operations.");
42
44
  }
43
45
  return original.transaction(async (tx) => {
44
- await tx.execute({
45
- sql: `SELECT set_config('app.current_tenant_id', $1, true)`,
46
- params: [tenantId],
47
- });
46
+ await setTenantConfig(tx, tenantId);
48
47
  return tx.execute(query);
49
48
  });
50
49
  };
50
+ // Typed as the caller's own Drizzle instance so `tx` keeps its query
51
+ // builder (`tx.select()...`) inside `transaction`.
51
52
  return {
52
53
  execute: wrappedExecute,
53
54
  transaction: wrappedTransaction,
@@ -1 +1 @@
1
- {"version":3,"file":"drizzle.js","sourceRoot":"","sources":["../../src/adapters/drizzle.ts"],"names":[],"mappings":";;;AAuEA,gCAOC;AA9ED,wDAAiD;AAUjD,MAAa,cAAe,SAAQ,6BAAW;IAC7C,YAAY,IAAa;QACvB,KAAK,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAED;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAe,EAAE,SAAuB;QACjD,MAAM,QAAQ,GAAG,EAAE,CAAC;QAEpB,MAAM,kBAAkB,GAAG,KAAK,EAAK,EAAmC,EAAc,EAAE;YACtF,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CACb,sDAAsD;oBACtD,yDAAyD,CAC1D,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,EAAe,EAAE,EAAE;gBACpD,MAAM,EAAE,CAAC,OAAO,CAAC;oBACf,GAAG,EAAE,sDAAsD;oBAC3D,MAAM,EAAE,CAAC,QAAQ,CAAC;iBACnB,CAAC,CAAC;gBACH,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,MAAM,cAAc,GAAG,KAAK,EAAE,KAAc,EAAoB,EAAE;YAChE,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CACb,sDAAsD;oBACtD,yDAAyD,CAC1D,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,EAAe,EAAE,EAAE;gBACpD,MAAM,EAAE,CAAC,OAAO,CAAC;oBACf,GAAG,EAAE,sDAAsD;oBAC3D,MAAM,EAAE,CAAC,QAAQ,CAAC;iBACnB,CAAC,CAAC;gBACH,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,cAAc;YACvB,WAAW,EAAE,kBAAkB;SAChC,CAAC;IACJ,CAAC;CACF;AA1DD,wCA0DC;AAED,+DAA+D;AAC/D,SAAgB,UAAU,CACxB,EAAe,EACf,SAAuB,EACvB,IAAa;IAEb,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED,4CAA4C;AAC/B,QAAA,eAAe,GAAG,UAAU,CAAC"}
1
+ {"version":3,"file":"drizzle.js","sourceRoot":"","sources":["../../src/adapters/drizzle.ts"],"names":[],"mappings":";;;AA4EA,gCAOC;AAnFD,wDAAiD;AAUjD,gFAAgF;AAChF,KAAK,UAAU,eAAe,CAAC,EAAe,EAAE,QAAgB;IAC9D,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IAC5C,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA,8CAA8C,QAAQ,SAAS,CAAC,CAAC;AACvF,CAAC;AAED,MAAa,cAAe,SAAQ,6BAAW;IAC7C,YAAY,IAAa;QACvB,KAAK,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAED;;;;;;;;;;OAUG;IACH,UAAU,CACR,EAAK,EACL,SAAuB;QAEvB,MAAM,QAAQ,GAAG,EAAE,CAAC;QAEpB,MAAM,kBAAkB,GAAG,KAAK,EAAK,EAAmC,EAAc,EAAE;YACtF,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CACb,sDAAsD;oBACtD,yDAAyD,CAC1D,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,EAAe,EAAE,EAAE;gBACpD,MAAM,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;gBACpC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,MAAM,cAAc,GAAG,KAAK,EAAE,KAAc,EAAoB,EAAE;YAChE,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CACb,sDAAsD;oBACtD,yDAAyD,CAC1D,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,EAAe,EAAE,EAAE;gBACpD,MAAM,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;gBACpC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,qEAAqE;QACrE,mDAAmD;QACnD,OAAO;YACL,OAAO,EAAE,cAAc;YACvB,WAAW,EAAE,kBAAkB;SACiB,CAAC;IACrD,CAAC;CACF;AAzDD,wCAyDC;AAED,+DAA+D;AAC/D,SAAgB,UAAU,CACxB,EAAK,EACL,SAAuB,EACvB,IAAa;IAEb,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED,4CAA4C;AAC/B,QAAA,eAAe,GAAG,UAAU,CAAC"}
@@ -3,7 +3,7 @@ import pg from "pg";
3
3
  interface PrismaClientLike {
4
4
  $extends: (extension: unknown) => PrismaClientLike;
5
5
  $executeRaw: (query: TemplateStringsArray, ...values: unknown[]) => Promise<number>;
6
- $transaction: <T>(fn: (tx: PrismaClientLike) => Promise<T>) => Promise<T>;
6
+ $transaction: (queries: unknown[]) => Promise<unknown[]>;
7
7
  }
8
8
  export declare class PrismaAdapter extends BaseAdapter {
9
9
  constructor(pool: pg.Pool);
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/adapters/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,MAAM,IAAI,CAAC;AAIpB,UAAU,gBAAgB;IACxB,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,gBAAgB,CAAC;IACnD,WAAW,EAAE,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpF,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAC3E;AAED,qBAAa,aAAc,SAAQ,WAAW;gBAChC,IAAI,EAAE,EAAE,CAAC,IAAI;IAIzB,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,MAAM,GAAG,gBAAgB;CAahF;AAED,wBAAgB,UAAU,CACxB,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,MAAM,EACvB,IAAI,EAAE,EAAE,CAAC,IAAI,GACZ,gBAAgB,CAGlB"}
1
+ {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/adapters/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,MAAM,IAAI,CAAC;AAIpB,UAAU,gBAAgB;IACxB,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,gBAAgB,CAAC;IACnD,WAAW,EAAE,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpF,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CAC1D;AAED,qBAAa,aAAc,SAAQ,WAAW;gBAChC,IAAI,EAAE,EAAE,CAAC,IAAI;IAIzB,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,MAAM,GAAG,gBAAgB;CAoBhF;AAED,wBAAgB,UAAU,CACxB,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,MAAM,EACvB,IAAI,EAAE,EAAE,CAAC,IAAI,GACZ,gBAAgB,CAGlB"}
@@ -10,12 +10,19 @@ class PrismaAdapter extends base_adapter_js_1.BaseAdapter {
10
10
  withTenant(prisma, contextFn) {
11
11
  return prisma.$extends({
12
12
  query: {
13
- $allOperations({ args, query }) {
13
+ async $allOperations({ args, query }) {
14
14
  const tenantId = contextFn();
15
- return prisma.$transaction(async (tx) => {
16
- await tx.$executeRaw `SELECT set_config('app.current_tenant_id', ${tenantId}, true)`;
17
- return query(args);
18
- });
15
+ if (!tenantId) {
16
+ throw new Error("Tenant context is required for database operations.");
17
+ }
18
+ // Batch form: Prisma runs both statements in order on one connection
19
+ // inside one transaction. `query(args)` does not inherit an
20
+ // interactive `tx`, so the callback form would run it elsewhere.
21
+ const [, result] = await prisma.$transaction([
22
+ prisma.$executeRaw `SELECT set_config('app.current_tenant_id', ${tenantId}, true)`,
23
+ query(args),
24
+ ]);
25
+ return result;
19
26
  },
20
27
  },
21
28
  });
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.js","sourceRoot":"","sources":["../../src/adapters/prisma.ts"],"names":[],"mappings":";;;AA+BA,gCAOC;AAtCD,wDAAiD;AAWjD,MAAa,aAAc,SAAQ,6BAAW;IAC5C,YAAY,IAAa;QACvB,KAAK,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAED,UAAU,CAAC,MAAwB,EAAE,SAAuB;QAC1D,OAAO,MAAM,CAAC,QAAQ,CAAC;YACrB,KAAK,EAAE;gBACL,cAAc,CAAC,EAAE,IAAI,EAAE,KAAK,EAAiE;oBAC3F,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;oBAC7B,OAAO,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,EAAoB,EAAE,EAAE;wBACxD,MAAM,EAAE,CAAC,WAAW,CAAA,8CAA8C,QAAQ,SAAS,CAAC;wBACpF,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC;oBACrB,CAAC,CAAC,CAAC;gBACL,CAAC;aACF;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAlBD,sCAkBC;AAED,SAAgB,UAAU,CACxB,MAAwB,EACxB,SAAuB,EACvB,IAAa;IAEb,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;IACxC,OAAO,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC/C,CAAC"}
1
+ {"version":3,"file":"prisma.js","sourceRoot":"","sources":["../../src/adapters/prisma.ts"],"names":[],"mappings":";;;AAsCA,gCAOC;AA7CD,wDAAiD;AAWjD,MAAa,aAAc,SAAQ,6BAAW;IAC5C,YAAY,IAAa;QACvB,KAAK,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAED,UAAU,CAAC,MAAwB,EAAE,SAAuB;QAC1D,OAAO,MAAM,CAAC,QAAQ,CAAC;YACrB,KAAK,EAAE;gBACL,KAAK,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,KAAK,EAAiE;oBACjG,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;oBAC7B,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACd,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;oBACzE,CAAC;oBACD,qEAAqE;oBACrE,4DAA4D;oBAC5D,iEAAiE;oBACjE,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC;wBAC3C,MAAM,CAAC,WAAW,CAAA,8CAA8C,QAAQ,SAAS;wBACjF,KAAK,CAAC,IAAI,CAAC;qBACZ,CAAC,CAAC;oBACH,OAAO,MAAM,CAAC;gBAChB,CAAC;aACF;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAzBD,sCAyBC;AAED,SAAgB,UAAU,CACxB,MAAwB,EACxB,SAAuB,EACvB,IAAa;IAEb,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;IACxC,OAAO,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC/C,CAAC"}
@@ -1,11 +1,43 @@
1
1
  interface PrismaClientLike {
2
2
  $extends: (extension: unknown) => PrismaClientLike;
3
- $executeRawUnsafe: (query: string, ...values: unknown[]) => Promise<number>;
4
- $transaction: <T>(fn: (tx: PrismaClientLike) => Promise<T>) => Promise<T>;
3
+ $disconnect: () => Promise<void>;
5
4
  }
5
+ type PrismaConstructor = new (options: {
6
+ datasources: {
7
+ db: {
8
+ url: string;
9
+ };
10
+ };
11
+ }) => PrismaClientLike;
12
+ /**
13
+ * Prisma adapter for SCHEMA_PER_TENANT isolation.
14
+ *
15
+ * Prisma schema-qualifies every table with the datasource URL's `schema`
16
+ * parameter, so `search_path` cannot route its queries. Each tenant therefore
17
+ * gets its own Prisma client whose datasource URL names the tenant's schema.
18
+ * Clients are cached per tenant slug, least recently used first out.
19
+ *
20
+ * Usage:
21
+ * const adapter = new SchemaPrismaAdapter(PrismaClient, baseUrl);
22
+ * const prisma = adapter.getClient('acme_corp');
23
+ * const rows = await prisma.someModel.findMany();
24
+ */
6
25
  export declare class SchemaPrismaAdapter {
7
- withTenant(prisma: PrismaClientLike, contextFn: () => string): PrismaClientLike;
26
+ private readonly PrismaClient;
27
+ private readonly baseDatasourceUrl;
28
+ private readonly maxClients;
29
+ private readonly clients;
30
+ constructor(PrismaClient: PrismaConstructor, baseDatasourceUrl: string, maxClients?: number);
31
+ /** Returns a Prisma client bound to the tenant's schema. */
32
+ getClient(tenantSlug: string): PrismaClientLike;
33
+ /** Disconnects all cached Prisma clients. Call during application shutdown. */
34
+ disconnectAll(): Promise<void>;
8
35
  }
9
- export declare function withSchemaTenant(prisma: PrismaClientLike, contextFn: () => string): PrismaClientLike;
36
+ /**
37
+ * @deprecated Cannot isolate tenants: Prisma ignores `search_path` and the
38
+ * wrapped query does not run on the transaction that would set it. Always
39
+ * throws. Use `new SchemaPrismaAdapter(PrismaClient, url).getClient(slug)`.
40
+ */
41
+ export declare function withSchemaTenant(_prisma: unknown, _contextFn: () => string): never;
10
42
  export {};
11
43
  //# sourceMappingURL=schema-prisma.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-prisma.d.ts","sourceRoot":"","sources":["../../src/adapters/schema-prisma.ts"],"names":[],"mappings":"AAKA,UAAU,gBAAgB;IACxB,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,gBAAgB,CAAC;IACnD,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAC3E;AAED,qBAAa,mBAAmB;IAC9B,UAAU,CACR,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,MAAM,GACtB,gBAAgB;CAuBpB;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,MAAM,GACtB,gBAAgB,CAGlB"}
1
+ {"version":3,"file":"schema-prisma.d.ts","sourceRoot":"","sources":["../../src/adapters/schema-prisma.ts"],"names":[],"mappings":"AAKA,UAAU,gBAAgB;IACxB,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,gBAAgB,CAAC;IACnD,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,KAAK,iBAAiB,GAAG,KAAK,OAAO,EAAE;IAAE,WAAW,EAAE;QAAE,EAAE,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,KAAK,gBAAgB,CAAC;AAErG;;;;;;;;;;;;GAYG;AACH,qBAAa,mBAAmB;IAI5B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAL7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4C;gBAGjD,YAAY,EAAE,iBAAiB,EAC/B,iBAAiB,EAAE,MAAM,EACzB,UAAU,GAAE,MAAW;IAG1C,4DAA4D;IAC5D,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB;IAsB/C,+EAA+E;IACzE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAKrC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,MAAM,GACvB,KAAK,CAKP"}
@@ -4,26 +4,64 @@ exports.SchemaPrismaAdapter = void 0;
4
4
  exports.withSchemaTenant = withSchemaTenant;
5
5
  const core_1 = require("@stratum-hq/core");
6
6
  const manager_js_1 = require("../schema/manager.js");
7
+ /**
8
+ * Prisma adapter for SCHEMA_PER_TENANT isolation.
9
+ *
10
+ * Prisma schema-qualifies every table with the datasource URL's `schema`
11
+ * parameter, so `search_path` cannot route its queries. Each tenant therefore
12
+ * gets its own Prisma client whose datasource URL names the tenant's schema.
13
+ * Clients are cached per tenant slug, least recently used first out.
14
+ *
15
+ * Usage:
16
+ * const adapter = new SchemaPrismaAdapter(PrismaClient, baseUrl);
17
+ * const prisma = adapter.getClient('acme_corp');
18
+ * const rows = await prisma.someModel.findMany();
19
+ */
7
20
  class SchemaPrismaAdapter {
8
- withTenant(prisma, contextFn) {
9
- return prisma.$extends({
10
- query: {
11
- $allOperations({ args, query, }) {
12
- const tenantSlug = (0, core_1.validateSlug)(contextFn());
13
- const schemaName = (0, manager_js_1.tenantSchemaName)(tenantSlug);
14
- return prisma.$transaction(async (tx) => {
15
- // schemaName is derived from a validated slug — safe for interpolation.
16
- await tx.$executeRawUnsafe(`SET LOCAL search_path TO ${schemaName}, public`);
17
- return query(args);
18
- });
19
- },
20
- },
21
- });
21
+ PrismaClient;
22
+ baseDatasourceUrl;
23
+ maxClients;
24
+ clients = new Map();
25
+ constructor(PrismaClient, baseDatasourceUrl, maxClients = 50) {
26
+ this.PrismaClient = PrismaClient;
27
+ this.baseDatasourceUrl = baseDatasourceUrl;
28
+ this.maxClients = maxClients;
29
+ }
30
+ /** Returns a Prisma client bound to the tenant's schema. */
31
+ getClient(tenantSlug) {
32
+ const schemaName = (0, manager_js_1.tenantSchemaName)((0, core_1.validateSlug)(tenantSlug));
33
+ const existing = this.clients.get(schemaName);
34
+ if (existing) {
35
+ this.clients.delete(schemaName);
36
+ this.clients.set(schemaName, existing);
37
+ return existing;
38
+ }
39
+ if (this.clients.size >= this.maxClients) {
40
+ const [oldestKey, oldest] = this.clients.entries().next().value;
41
+ this.clients.delete(oldestKey);
42
+ void oldest.$disconnect().catch(() => { });
43
+ }
44
+ const url = new URL(this.baseDatasourceUrl);
45
+ url.searchParams.set("schema", schemaName);
46
+ const client = new this.PrismaClient({ datasources: { db: { url: url.toString() } } });
47
+ this.clients.set(schemaName, client);
48
+ return client;
49
+ }
50
+ /** Disconnects all cached Prisma clients. Call during application shutdown. */
51
+ async disconnectAll() {
52
+ const clients = Array.from(this.clients.values());
53
+ this.clients.clear();
54
+ await Promise.all(clients.map((client) => client.$disconnect()));
22
55
  }
23
56
  }
24
57
  exports.SchemaPrismaAdapter = SchemaPrismaAdapter;
25
- function withSchemaTenant(prisma, contextFn) {
26
- const adapter = new SchemaPrismaAdapter();
27
- return adapter.withTenant(prisma, contextFn);
58
+ /**
59
+ * @deprecated Cannot isolate tenants: Prisma ignores `search_path` and the
60
+ * wrapped query does not run on the transaction that would set it. Always
61
+ * throws. Use `new SchemaPrismaAdapter(PrismaClient, url).getClient(slug)`.
62
+ */
63
+ function withSchemaTenant(_prisma, _contextFn) {
64
+ throw new Error("withSchemaTenant cannot isolate Prisma queries by schema. " +
65
+ "Use new SchemaPrismaAdapter(PrismaClient, datasourceUrl).getClient(tenantSlug) instead.");
28
66
  }
29
67
  //# sourceMappingURL=schema-prisma.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-prisma.js","sourceRoot":"","sources":["../../src/adapters/schema-prisma.ts"],"names":[],"mappings":";;;AAwCA,4CAMC;AA9CD,2CAAgD;AAChD,qDAAwD;AAUxD,MAAa,mBAAmB;IAC9B,UAAU,CACR,MAAwB,EACxB,SAAuB;QAEvB,OAAO,MAAM,CAAC,QAAQ,CAAC;YACrB,KAAK,EAAE;gBACL,cAAc,CAAC,EACb,IAAI,EACJ,KAAK,GAIN;oBACC,MAAM,UAAU,GAAG,IAAA,mBAAY,EAAC,SAAS,EAAE,CAAC,CAAC;oBAC7C,MAAM,UAAU,GAAG,IAAA,6BAAgB,EAAC,UAAU,CAAC,CAAC;oBAChD,OAAO,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,EAAoB,EAAE,EAAE;wBACxD,wEAAwE;wBACxE,MAAM,EAAE,CAAC,iBAAiB,CACxB,4BAA4B,UAAU,UAAU,CACjD,CAAC;wBACF,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC;oBACrB,CAAC,CAAC,CAAC;gBACL,CAAC;aACF;SACF,CAAC,CAAC;IACL,CAAC;CACF;AA3BD,kDA2BC;AAED,SAAgB,gBAAgB,CAC9B,MAAwB,EACxB,SAAuB;IAEvB,MAAM,OAAO,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAC1C,OAAO,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC/C,CAAC"}
1
+ {"version":3,"file":"schema-prisma.js","sourceRoot":"","sources":["../../src/adapters/schema-prisma.ts"],"names":[],"mappings":";;;AAsEA,4CAQC;AA9ED,2CAAgD;AAChD,qDAAwD;AAWxD;;;;;;;;;;;;GAYG;AACH,MAAa,mBAAmB;IAIX;IACA;IACA;IALF,OAAO,GAAkC,IAAI,GAAG,EAAE,CAAC;IAEpE,YACmB,YAA+B,EAC/B,iBAAyB,EACzB,aAAqB,EAAE;QAFvB,iBAAY,GAAZ,YAAY,CAAmB;QAC/B,sBAAiB,GAAjB,iBAAiB,CAAQ;QACzB,eAAU,GAAV,UAAU,CAAa;IACvC,CAAC;IAEJ,4DAA4D;IAC5D,SAAS,CAAC,UAAkB;QAC1B,MAAM,UAAU,GAAG,IAAA,6BAAgB,EAAC,IAAA,mBAAY,EAAC,UAAU,CAAC,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YACvC,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACzC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAmC,CAAC;YAC9F,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC/B,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC5C,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC5C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACvF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACrC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,aAAa;QACjB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;CACF;AAtCD,kDAsCC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAC9B,OAAgB,EAChB,UAAwB;IAExB,MAAM,IAAI,KAAK,CACb,4DAA4D;QAC1D,yFAAyF,CAC5F,CAAC;AACJ,CAAC"}
@@ -1,13 +1,24 @@
1
1
  import pg from "pg";
2
+ export interface SchemaAdapterOptions {
3
+ /**
4
+ * Schemas searched after the tenant schema, for example one holding
5
+ * extension functions or types. Default: none. An unqualified table missing
6
+ * from the tenant schema resolves in these schemas, so list only schemas
7
+ * that hold no tenant data.
8
+ */
9
+ extraSearchPath?: string[];
10
+ }
2
11
  export declare class SchemaRawAdapter {
3
12
  private pool;
4
- constructor(pool: pg.Pool);
13
+ private readonly extraSearchPath;
14
+ constructor(pool: pg.Pool, options?: SchemaAdapterOptions);
5
15
  query<T extends pg.QueryResultRow = pg.QueryResultRow>(tenantSlug: string, text: string, values?: unknown[]): Promise<pg.QueryResult<T>>;
6
16
  /**
7
17
  * Executes a callback within a transaction scoped to the tenant's schema.
8
- * Sets `search_path` to `tenant_{slug}, public` for the duration of the transaction.
18
+ * Sets `search_path` to `tenant_{slug}`, followed only by any `extraSearchPath`
19
+ * schemas, for the duration of the transaction.
9
20
  */
10
21
  executeWithTenantContext<T>(tenantSlug: string, fn: (client: pg.PoolClient) => Promise<T>): Promise<T>;
11
22
  }
12
- export declare function createSchemaTenantPool(pool: pg.Pool, contextFn: () => string): SchemaRawAdapter;
23
+ export declare function createSchemaTenantPool(pool: pg.Pool, contextFn: () => string, options?: SchemaAdapterOptions): SchemaRawAdapter;
13
24
  //# sourceMappingURL=schema-raw.d.ts.map
@@ -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,qBAAa,gBAAgB;IACf,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,EAAE,CAAC,IAAI;IAE3B,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;;;OAGG;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;CAmBd;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,SAAS,EAAE,MAAM,MAAM,GACtB,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;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"}
@@ -7,8 +7,11 @@ const manager_js_1 = require("../schema/manager.js");
7
7
  const session_js_1 = require("../schema/session.js");
8
8
  class SchemaRawAdapter {
9
9
  pool;
10
- constructor(pool) {
10
+ extraSearchPath;
11
+ constructor(pool, options = {}) {
11
12
  this.pool = pool;
13
+ // Validate up front so a bad entry fails at construction, not per query.
14
+ this.extraSearchPath = (options.extraSearchPath ?? []).map(manager_js_1.validateSchemaName);
12
15
  }
13
16
  async query(tenantSlug, text, values) {
14
17
  return this.executeWithTenantContext(tenantSlug, async (client) => {
@@ -17,7 +20,8 @@ class SchemaRawAdapter {
17
20
  }
18
21
  /**
19
22
  * Executes a callback within a transaction scoped to the tenant's schema.
20
- * Sets `search_path` to `tenant_{slug}, public` for the duration of the transaction.
23
+ * Sets `search_path` to `tenant_{slug}`, followed only by any `extraSearchPath`
24
+ * schemas, for the duration of the transaction.
21
25
  */
22
26
  async executeWithTenantContext(tenantSlug, fn) {
23
27
  const safe = (0, core_1.validateSlug)(tenantSlug);
@@ -26,7 +30,7 @@ class SchemaRawAdapter {
26
30
  try {
27
31
  await client.query("BEGIN");
28
32
  // SET LOCAL is transaction-scoped; schemaName is derived from a validated slug.
29
- await client.query(`SET LOCAL search_path TO ${schemaName}, public`);
33
+ await client.query(`SET LOCAL search_path TO ${(0, session_js_1.tenantSearchPath)(schemaName, this.extraSearchPath)}`);
30
34
  const result = await fn(client);
31
35
  await client.query("COMMIT");
32
36
  return result;
@@ -42,8 +46,8 @@ class SchemaRawAdapter {
42
46
  }
43
47
  }
44
48
  exports.SchemaRawAdapter = SchemaRawAdapter;
45
- function createSchemaTenantPool(pool, contextFn) {
46
- const adapter = new SchemaRawAdapter(pool);
49
+ function createSchemaTenantPool(pool, contextFn, options = {}) {
50
+ const adapter = new SchemaRawAdapter(pool, options);
47
51
  return new Proxy(adapter, {
48
52
  get(target, prop) {
49
53
  if (prop === "query") {
@@ -1 +1 @@
1
- {"version":3,"file":"schema-raw.js","sourceRoot":"","sources":["../../src/adapters/schema-raw.ts"],"names":[],"mappings":";;;AA8CA,wDAcC;AA3DD,2CAAgD;AAChD,qDAAwD;AACxD,qDAAuD;AAEvD,MAAa,gBAAgB;IACP;IAApB,YAAoB,IAAa;QAAb,SAAI,GAAJ,IAAI,CAAS;IAAG,CAAC;IAErC,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;;;OAGG;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,CAAC,4BAA4B,UAAU,UAAU,CAAC,CAAC;YACrE,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;AAvCD,4CAuCC;AAED,SAAgB,sBAAsB,CACpC,IAAa,EACb,SAAuB;IAEvB,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC3C,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":";;;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 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/database/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAKpB;;;GAGG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,MAAM,EACjC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,MAAM,EACjC,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,MAAM,EACjC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/database/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAOpB;;;GAGG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAS1D;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,MAAM,EACjC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,MAAM,EACjC,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,MAAM,EACjC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf"}
@@ -6,13 +6,19 @@ exports.createDatabase = createDatabase;
6
6
  exports.dropDatabase = dropDatabase;
7
7
  const core_1 = require("@stratum-hq/core");
8
8
  const DB_PREFIX = "stratum_tenant_";
9
+ // PostgreSQL silently truncates database names longer than this many bytes.
10
+ const MAX_IDENTIFIER_BYTES = 63;
9
11
  /**
10
12
  * Returns the database name for a given tenant slug.
11
13
  * Format: stratum_tenant_{slug}
12
14
  */
13
15
  function getDatabaseName(tenantSlug) {
14
16
  (0, core_1.validateSlug)(tenantSlug);
15
- return `${DB_PREFIX}${tenantSlug}`;
17
+ const dbName = `${DB_PREFIX}${tenantSlug}`;
18
+ if (Buffer.byteLength(dbName) > MAX_IDENTIFIER_BYTES) {
19
+ throw new Error(`Database name for tenant slug "${tenantSlug}" exceeds ${MAX_IDENTIFIER_BYTES} bytes`);
20
+ }
21
+ return dbName;
16
22
  }
17
23
  /**
18
24
  * Checks whether the per-tenant database exists.
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/database/manager.ts"],"names":[],"mappings":";;AASA,0CAGC;AAMD,wCAUC;AASD,wCAiBC;AAQD,oCAMC;AAnED,2CAAgD;AAEhD,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAEpC;;;GAGG;AACH,SAAgB,eAAe,CAAC,UAAkB;IAChD,IAAA,mBAAY,EAAC,UAAU,CAAC,CAAC;IACzB,OAAO,GAAG,SAAS,GAAG,UAAU,EAAE,CAAC;AACrC,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,cAAc,CAClC,MAAiC,EACjC,UAAkB;IAElB,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,uEAAuE,EACvE,CAAC,MAAM,CAAC,CACT,CAAC;IACF,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAC5B,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,cAAc,CAClC,MAAiC,EACjC,UAAkB,EAClB,UAAmB;IAEnB,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAC3C,+FAA+F;IAC/F,8FAA8F;IAC9F,IAAI,GAAG,GAAG,oBAAoB,MAAM,GAAG,CAAC;IACxC,IAAI,UAAU,EAAE,CAAC;QACf,iEAAiE;QACjE,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,oCAAoC,UAAU,GAAG,CAAC,CAAC;QACrE,CAAC;QACD,GAAG,IAAI,cAAc,UAAU,GAAG,CAAC;IACrC,CAAC;IACD,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,YAAY,CAChC,MAAiC,EACjC,UAAkB;IAElB,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,MAAM,CAAC,KAAK,CAAC,4BAA4B,MAAM,GAAG,CAAC,CAAC;AAC5D,CAAC"}
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/database/manager.ts"],"names":[],"mappings":";;AAWA,0CASC;AAMD,wCAUC;AASD,wCAiBC;AAQD,oCAMC;AA3ED,2CAAgD;AAEhD,MAAM,SAAS,GAAG,iBAAiB,CAAC;AACpC,4EAA4E;AAC5E,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEhC;;;GAGG;AACH,SAAgB,eAAe,CAAC,UAAkB;IAChD,IAAA,mBAAY,EAAC,UAAU,CAAC,CAAC;IACzB,MAAM,MAAM,GAAG,GAAG,SAAS,GAAG,UAAU,EAAE,CAAC;IAC3C,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,oBAAoB,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CACb,kCAAkC,UAAU,aAAa,oBAAoB,QAAQ,CACtF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,cAAc,CAClC,MAAiC,EACjC,UAAkB;IAElB,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,uEAAuE,EACvE,CAAC,MAAM,CAAC,CACT,CAAC;IACF,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAC5B,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,cAAc,CAClC,MAAiC,EACjC,UAAkB,EAClB,UAAmB;IAEnB,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAC3C,+FAA+F;IAC/F,8FAA8F;IAC9F,IAAI,GAAG,GAAG,oBAAoB,MAAM,GAAG,CAAC;IACxC,IAAI,UAAU,EAAE,CAAC;QACf,iEAAiE;QACjE,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,oCAAoC,UAAU,GAAG,CAAC,CAAC;QACrE,CAAC;QACD,GAAG,IAAI,cAAc,UAAU,GAAG,CAAC;IACrC,CAAC;IACD,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,YAAY,CAChC,MAAiC,EACjC,UAAkB;IAElB,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,MAAM,CAAC,KAAK,CAAC,4BAA4B,MAAM,GAAG,CAAC,CAAC;AAC5D,CAAC"}
@@ -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;IAyBtE;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,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"}
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DatabasePoolManager = void 0;
7
7
  const pg_1 = __importDefault(require("pg"));
8
- const core_1 = require("@stratum-hq/core");
8
+ const manager_js_1 = require("./manager.js");
9
9
  /**
10
10
  * Manages a collection of per-tenant pg.Pool instances.
11
11
  *
@@ -30,7 +30,7 @@ class DatabasePoolManager {
30
30
  * multi-region deployments where the same slug may exist in different regions.
31
31
  */
32
32
  async getPool(tenantSlug, regionId) {
33
- (0, core_1.validateSlug)(tenantSlug);
33
+ const dbName = (0, manager_js_1.getDatabaseName)(tenantSlug);
34
34
  const poolKey = regionId ? `${regionId}:${tenantSlug}` : tenantSlug;
35
35
  const existing = this.pools.get(poolKey);
36
36
  if (existing) {
@@ -41,7 +41,6 @@ class DatabasePoolManager {
41
41
  if (this.pools.size >= this.maxPools) {
42
42
  await this.evictLRU();
43
43
  }
44
- const dbName = `stratum_tenant_${tenantSlug}`;
45
44
  const pool = new pg_1.default.Pool({
46
45
  ...this.baseConfig,
47
46
  database: dbName,
@@ -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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"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { BaseAdapter } from "./base-adapter.js";
2
2
  export { RawAdapter, createTenantPool } from "./adapters/raw.js";
3
3
  export { PrismaAdapter, withTenant as prismaWithTenant } from "./adapters/prisma.js";
4
4
  export { SchemaRawAdapter, createSchemaTenantPool, } from "./adapters/schema-raw.js";
5
+ export type { SchemaAdapterOptions } from "./adapters/schema-raw.js";
5
6
  export { SchemaPrismaAdapter, withSchemaTenant, } from "./adapters/schema-prisma.js";
6
7
  export { createPolicy, dropPolicy, enableRLS, disableRLS, isRLSEnabled, } from "./rls/manager.js";
7
8
  export { setTenantContext, resetTenantContext, withTenantContext, withRlsBypass, getCurrentTenantId, } from "./rls/session.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,IAAI,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACrF,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,qBAAqB,EAClC,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,IAAI,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxG,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,IAAI,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACrF,OAAO,EACL,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,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,qBAAqB,EAClC,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,IAAI,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxG,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.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,kDAAqF;AAA5E,0GAAA,aAAa,OAAA;AAAE,6GAAA,UAAU,OAAoB;AACtD,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,6HAAA,SAAS,OAAyB;AAClC,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,wDAAwG;AAA/F,gHAAA,gBAAgB,OAAA;AAAE,wHAAA,eAAe,OAA4B;AACtE,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,kDAAqF;AAA5E,0GAAA,aAAa,OAAA;AAAE,6GAAA,UAAU,OAAoB;AACtD,0DAGkC;AAFhC,iHAAA,gBAAgB,OAAA;AAChB,uHAAA,sBAAsB,OAAA;AAGxB,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,6HAAA,SAAS,OAAyB;AAClC,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,wDAAwG;AAA/F,gHAAA,gBAAgB,OAAA;AAAE,wHAAA,eAAe,OAA4B;AACtE,oDAI+B;AAH7B,4GAAA,cAAc,OAAA;AACd,+GAAA,UAAU,OAAqB;AAC/B,oHAAA,eAAe,OAA0B"}
@@ -1,4 +1,5 @@
1
1
  import pg from "pg";
2
+ export declare function validateSchemaName(schemaName: string): string;
2
3
  export declare function tenantSchemaName(tenantSlug: string): string;
3
4
  export declare function createSchema(client: pg.PoolClient, tenantSlug: string): Promise<void>;
4
5
  export declare function dropSchema(client: pg.PoolClient, tenantSlug: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/schema/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAkBpB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,EAAE,CAAC,UAAU,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CAKnB;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAMf"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/schema/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpB,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAK7D;AAaD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ3D;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,EAAE,CAAC,UAAU,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CAKnB;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateSchemaName = validateSchemaName;
3
4
  exports.tenantSchemaName = tenantSchemaName;
4
5
  exports.createSchema = createSchema;
5
6
  exports.dropSchema = dropSchema;
@@ -20,12 +21,20 @@ function validateTableName(tableName) {
20
21
  }
21
22
  return tableName;
22
23
  }
24
+ // PostgreSQL silently truncates identifiers longer than this many bytes.
25
+ const MAX_IDENTIFIER_BYTES = 63;
23
26
  function tenantSchemaName(tenantSlug) {
24
- return `tenant_${tenantSlug}`;
27
+ const schemaName = `tenant_${tenantSlug}`;
28
+ if (Buffer.byteLength(schemaName) > MAX_IDENTIFIER_BYTES) {
29
+ throw new Error(`Schema name for tenant slug "${tenantSlug}" exceeds ${MAX_IDENTIFIER_BYTES} bytes`);
30
+ }
31
+ return schemaName;
25
32
  }
26
33
  async function createSchema(client, tenantSlug) {
27
34
  const schemaName = validateSchemaName(tenantSchemaName(tenantSlug));
28
- await client.query(`CREATE SCHEMA IF NOT EXISTS ${schemaName}`);
35
+ // No IF NOT EXISTS: an existing schema may hold another tenant's data, so
36
+ // provisioning must fail rather than adopt it.
37
+ await client.query(`CREATE SCHEMA ${schemaName}`);
29
38
  }
30
39
  async function dropSchema(client, tenantSlug) {
31
40
  const schemaName = validateSchemaName(tenantSchemaName(tenantSlug));
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/schema/manager.ts"],"names":[],"mappings":";;AAkBA,4CAEC;AAED,oCAMC;AAED,gCAMC;AAED,oCAUC;AAED,8CAOC;AAED,wDAUC;AAnED,yFAAyF;AACzF,SAAS,kBAAkB,CAAC,UAAkB;IAC5C,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,wBAAwB,UAAU,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,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;AAED,SAAgB,gBAAgB,CAAC,UAAkB;IACjD,OAAO,UAAU,UAAU,EAAE,CAAC;AAChC,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,UAAkB;IAElB,MAAM,UAAU,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC;IACpE,MAAM,MAAM,CAAC,KAAK,CAAC,+BAA+B,UAAU,EAAE,CAAC,CAAC;AAClE,CAAC;AAEM,KAAK,UAAU,UAAU,CAC9B,MAAqB,EACrB,UAAkB;IAElB,MAAM,UAAU,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC;IACpE,MAAM,MAAM,CAAC,KAAK,CAAC,yBAAyB,UAAU,UAAU,CAAC,CAAC;AACpE,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,UAAkB;IAElB,MAAM,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,sDAAsD,EACtD,CAAC,UAAU,CAAC,CACb,CAAC;IACF,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;AAC7C,CAAC;AAEM,KAAK,UAAU,iBAAiB,CACrC,MAAqB;IAErB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,iFAAiF,CAClF,CAAC;IACF,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC;AAEM,KAAK,UAAU,sBAAsB,CAC1C,MAAqB,EACrB,SAAiB,EACjB,UAAkB;IAElB,MAAM,SAAS,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC/C,MAAM,UAAU,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAClD,MAAM,MAAM,CAAC,KAAK,CAChB,8BAA8B,UAAU,IAAI,SAAS,iBAAiB,SAAS,iBAAiB,CACjG,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/schema/manager.ts"],"names":[],"mappings":";;AAGA,gDAKC;AAaD,4CAQC;AAED,oCAQC;AAED,gCAMC;AAED,oCAUC;AAED,8CAOC;AAED,wDAUC;AA9ED,yFAAyF;AACzF,SAAgB,kBAAkB,CAAC,UAAkB;IACnD,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,wBAAwB,UAAU,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,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;AAED,yEAAyE;AACzE,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEhC,SAAgB,gBAAgB,CAAC,UAAkB;IACjD,MAAM,UAAU,GAAG,UAAU,UAAU,EAAE,CAAC;IAC1C,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,oBAAoB,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACb,gCAAgC,UAAU,aAAa,oBAAoB,QAAQ,CACpF,CAAC;IACJ,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,UAAkB;IAElB,MAAM,UAAU,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC;IACpE,0EAA0E;IAC1E,+CAA+C;IAC/C,MAAM,MAAM,CAAC,KAAK,CAAC,iBAAiB,UAAU,EAAE,CAAC,CAAC;AACpD,CAAC;AAEM,KAAK,UAAU,UAAU,CAC9B,MAAqB,EACrB,UAAkB;IAElB,MAAM,UAAU,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC;IACpE,MAAM,MAAM,CAAC,KAAK,CAAC,yBAAyB,UAAU,UAAU,CAAC,CAAC;AACpE,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,UAAkB;IAElB,MAAM,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,sDAAsD,EACtD,CAAC,UAAU,CAAC,CACb,CAAC;IACF,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;AAC7C,CAAC;AAEM,KAAK,UAAU,iBAAiB,CACrC,MAAqB;IAErB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,iFAAiF,CAClF,CAAC;IACF,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC;AAEM,KAAK,UAAU,sBAAsB,CAC1C,MAAqB,EACrB,SAAiB,EACjB,UAAkB;IAElB,MAAM,SAAS,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC/C,MAAM,UAAU,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAClD,MAAM,MAAM,CAAC,KAAK,CAChB,8BAA8B,UAAU,IAAI,SAAS,iBAAiB,SAAS,iBAAiB,CACjG,CAAC;AACJ,CAAC"}
@@ -1,5 +1,14 @@
1
1
  import pg from "pg";
2
- export declare function setSchemaSearchPath(client: pg.PoolClient, tenantSlug: string): Promise<void>;
2
+ /**
3
+ * Builds the search_path list for a tenant: the tenant schema first, then any
4
+ * explicitly opted-in extra schemas (for example one holding extension
5
+ * functions). Every extra entry is validated as a plain identifier, the same
6
+ * way tenant schema names are. Note that an unqualified table missing from the
7
+ * tenant schema resolves in the extra schemas, so list only schemas that hold
8
+ * no tenant data.
9
+ */
10
+ export declare function tenantSearchPath(schemaName: string, extraSearchPath?: string[]): string;
11
+ export declare function setSchemaSearchPath(client: pg.PoolClient, tenantSlug: string, extraSearchPath?: string[]): Promise<void>;
3
12
  export declare function resetSearchPath(client: pg.PoolClient): Promise<void>;
4
13
  export declare function getCurrentSearchPath(client: pg.PoolClient): Promise<string>;
5
14
  //# sourceMappingURL=session.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/schema/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAIpB;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,GAAE,MAAM,EAAO,GAAG,MAAM,CAE3F;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,EAAE,CAAC,UAAU,EACrB,UAAU,EAAE,MAAM,EAClB,eAAe,GAAE,MAAM,EAAO,GAC7B,OAAO,CAAC,IAAI,CAAC,CAef;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,17 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tenantSearchPath = tenantSearchPath;
3
4
  exports.setSchemaSearchPath = setSchemaSearchPath;
4
5
  exports.resetSearchPath = resetSearchPath;
5
6
  exports.getCurrentSearchPath = getCurrentSearchPath;
6
7
  const core_1 = require("@stratum-hq/core");
7
8
  const manager_js_1 = require("./manager.js");
8
- async function setSchemaSearchPath(client, tenantSlug) {
9
+ /**
10
+ * Builds the search_path list for a tenant: the tenant schema first, then any
11
+ * explicitly opted-in extra schemas (for example one holding extension
12
+ * functions). Every extra entry is validated as a plain identifier, the same
13
+ * way tenant schema names are. Note that an unqualified table missing from the
14
+ * tenant schema resolves in the extra schemas, so list only schemas that hold
15
+ * no tenant data.
16
+ */
17
+ function tenantSearchPath(schemaName, extraSearchPath = []) {
18
+ return [schemaName, ...extraSearchPath.map(manager_js_1.validateSchemaName)].join(", ");
19
+ }
20
+ async function setSchemaSearchPath(client, tenantSlug, extraSearchPath = []) {
9
21
  // The schema name is interpolated (identifiers cannot be bound), so the slug
10
22
  // must be validated against the canonical slug charset first; validateSlug
11
23
  // 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));
14
- await client.query(`SET LOCAL search_path TO ${schemaName}, public`);
24
+ // transaction. The path is the tenant schema alone (pg_catalog is always
25
+ // searched), so a table missing from it errors instead of resolving to a
26
+ // shared table in public. Extra schemas are appended only when opted in.
27
+ const searchPath = tenantSearchPath((0, manager_js_1.tenantSchemaName)((0, core_1.validateSlug)(tenantSlug)), extraSearchPath);
28
+ await client.query(`SET LOCAL search_path TO ${searchPath}`);
29
+ // Outside a transaction SET LOCAL is a no-op with only a warning. SHOW
30
+ // reports unquoted identifiers folded to lower case.
31
+ const res = await client.query("SHOW search_path");
32
+ if (res.rows[0].search_path !== searchPath.toLowerCase()) {
33
+ throw new Error("setSchemaSearchPath must be called inside a transaction");
34
+ }
15
35
  }
16
36
  async function resetSearchPath(client) {
17
37
  await client.query("RESET search_path");
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/schema/session.ts"],"names":[],"mappings":";;AAYA,4CAEC;AAED,kDAmBC;AAED,0CAEC;AAED,oDAKC;AA7CD,2CAAgD;AAChD,6CAAoE;AAEpE;;;;;;;GAOG;AACH,SAAgB,gBAAgB,CAAC,UAAkB,EAAE,kBAA4B,EAAE;IACjF,OAAO,CAAC,UAAU,EAAE,GAAG,eAAe,CAAC,GAAG,CAAC,+BAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7E,CAAC;AAEM,KAAK,UAAU,mBAAmB,CACvC,MAAqB,EACrB,UAAkB,EAClB,kBAA4B,EAAE;IAE9B,6EAA6E;IAC7E,2EAA2E;IAC3E,uEAAuE;IACvE,yEAAyE;IACzE,yEAAyE;IACzE,yEAAyE;IACzE,MAAM,UAAU,GAAG,gBAAgB,CAAC,IAAA,6BAAgB,EAAC,IAAA,mBAAY,EAAC,UAAU,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;IACjG,MAAM,MAAM,CAAC,KAAK,CAAC,4BAA4B,UAAU,EAAE,CAAC,CAAC;IAC7D,uEAAuE;IACvE,qDAAqD;IACrD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAA0B,kBAAkB,CAAC,CAAC;IAC5E,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,KAAK,UAAU,CAAC,WAAW,EAAE,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;AACH,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": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Stratum PostgreSQL adapters for tenant-scoped query isolation",
5
5
  "keywords": [
6
6
  "multi-tenancy",
@@ -55,8 +55,9 @@
55
55
  "devDependencies": {
56
56
  "@prisma/client": "^5.10.0",
57
57
  "@types/pg": "^8.11.0",
58
+ "drizzle-orm": "^0.45.3",
58
59
  "typescript": "^5.4.0",
59
- "vitest": "^1.6.0"
60
+ "vitest": "^4.1.11"
60
61
  },
61
62
  "license": "MIT",
62
63
  "author": "Christian Crank",