@stratum-hq/db-adapters 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +39 -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/database-raw.d.ts +2 -0
  7. package/dist/adapters/database-raw.d.ts.map +1 -1
  8. package/dist/adapters/database-raw.js +36 -13
  9. package/dist/adapters/database-raw.js.map +1 -1
  10. package/dist/adapters/drizzle.d.ts +2 -2
  11. package/dist/adapters/drizzle.d.ts.map +1 -1
  12. package/dist/adapters/drizzle.js +9 -8
  13. package/dist/adapters/drizzle.js.map +1 -1
  14. package/dist/adapters/prisma.d.ts +1 -1
  15. package/dist/adapters/prisma.d.ts.map +1 -1
  16. package/dist/adapters/prisma.js +12 -5
  17. package/dist/adapters/prisma.js.map +1 -1
  18. package/dist/adapters/schema-prisma.d.ts +36 -4
  19. package/dist/adapters/schema-prisma.d.ts.map +1 -1
  20. package/dist/adapters/schema-prisma.js +55 -17
  21. package/dist/adapters/schema-prisma.js.map +1 -1
  22. package/dist/adapters/schema-raw.d.ts +14 -3
  23. package/dist/adapters/schema-raw.d.ts.map +1 -1
  24. package/dist/adapters/schema-raw.js +30 -8
  25. package/dist/adapters/schema-raw.js.map +1 -1
  26. package/dist/database/manager.d.ts.map +1 -1
  27. package/dist/database/manager.js +7 -1
  28. package/dist/database/manager.js.map +1 -1
  29. package/dist/database/pool-manager.d.ts +38 -7
  30. package/dist/database/pool-manager.d.ts.map +1 -1
  31. package/dist/database/pool-manager.js +103 -27
  32. package/dist/database/pool-manager.js.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/migration-helpers.js +1 -1
  37. package/dist/migration-helpers.js.map +1 -1
  38. package/dist/rls/manager.js +1 -1
  39. package/dist/rls/manager.js.map +1 -1
  40. package/dist/schema/manager.d.ts +1 -0
  41. package/dist/schema/manager.d.ts.map +1 -1
  42. package/dist/schema/manager.js +11 -2
  43. package/dist/schema/manager.js.map +1 -1
  44. package/dist/schema/session.d.ts +10 -1
  45. package/dist/schema/session.d.ts.map +1 -1
  46. package/dist/schema/session.js +24 -4
  47. package/dist/schema/session.js.map +1 -1
  48. package/package.json +4 -3
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,41 @@ 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
+
79
+ ## Database-per-tenant pools
80
+
81
+ `DatabasePoolManager` keeps one `pg.Pool` for each tenant database. Each `getPool(slug)` call holds the pool until you call `releasePool(slug)` with the same arguments. The manager never ends a held pool. Thus a request that is using a pool cannot lose it to eviction.
82
+
83
+ ```typescript
84
+ const pool = await poolManager.getPool("acme");
85
+ try {
86
+ await pool.query("SELECT 1");
87
+ } finally {
88
+ poolManager.releasePool("acme");
89
+ }
90
+ ```
91
+
92
+ When the pool count reaches `maxPools`, the manager ends the least recently used pool that no caller holds. If every pool is held, the count goes above `maxPools` until a caller releases one. A pool that you never release stays open until `closePool` or `closeAll`. `DatabaseRawAdapter` releases its pool after each call.
93
+
94
+ Concurrent first requests for one tenant share one pool.
95
+
61
96
  ## Security
62
97
 
63
98
  - 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"}
@@ -5,6 +5,8 @@ import { DatabasePoolManager } from "../database/pool-manager.js";
5
5
  *
6
6
  * Each query is routed to the dedicated database for the given tenant slug
7
7
  * by obtaining a connection from the tenant's pool via DatabasePoolManager.
8
+ * Each method releases its hold on the pool when it finishes, so the manager
9
+ * can evict the pool while no query uses it.
8
10
  */
9
11
  export declare class DatabaseRawAdapter {
10
12
  private readonly poolManager;
@@ -1 +1 @@
1
- {"version":3,"file":"database-raw.d.ts","sourceRoot":"","sources":["../../src/adapters/database-raw.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;GAKG;AACH,qBAAa,kBAAkB;IACjB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,mBAAmB;IAE7D;;;OAGG;IACG,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,cAAc,EACzD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAU7B;;;OAGG;IACG,wBAAwB,CAAC,CAAC,EAC9B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7C,OAAO,CAAC,CAAC,CAAC;CAed"}
1
+ {"version":3,"file":"database-raw.d.ts","sourceRoot":"","sources":["../../src/adapters/database-raw.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IACjB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,mBAAmB;IAE7D;;;OAGG;IACG,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,cAAc,EACzD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAc7B;;;OAGG;IACG,wBAAwB,CAAC,CAAC,EAC9B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7C,OAAO,CAAC,CAAC,CAAC;CA6Bd"}
@@ -6,6 +6,8 @@ exports.DatabaseRawAdapter = void 0;
6
6
  *
7
7
  * Each query is routed to the dedicated database for the given tenant slug
8
8
  * by obtaining a connection from the tenant's pool via DatabasePoolManager.
9
+ * Each method releases its hold on the pool when it finishes, so the manager
10
+ * can evict the pool while no query uses it.
9
11
  */
10
12
  class DatabaseRawAdapter {
11
13
  poolManager;
@@ -18,12 +20,17 @@ class DatabaseRawAdapter {
18
20
  */
19
21
  async query(tenantSlug, text, values) {
20
22
  const pool = await this.poolManager.getPool(tenantSlug);
21
- const client = await pool.connect();
22
23
  try {
23
- return await client.query(text, values);
24
+ const client = await pool.connect();
25
+ try {
26
+ return await client.query(text, values);
27
+ }
28
+ finally {
29
+ client.release();
30
+ }
24
31
  }
25
32
  finally {
26
- client.release();
33
+ this.poolManager.releasePool(tenantSlug);
27
34
  }
28
35
  }
29
36
  /**
@@ -32,19 +39,35 @@ class DatabaseRawAdapter {
32
39
  */
33
40
  async executeWithTenantContext(tenantSlug, queryFn) {
34
41
  const pool = await this.poolManager.getPool(tenantSlug);
35
- const client = await pool.connect();
36
42
  try {
37
- await client.query("BEGIN");
38
- const result = await queryFn(client);
39
- await client.query("COMMIT");
40
- return result;
41
- }
42
- catch (err) {
43
- await client.query("ROLLBACK");
44
- throw err;
43
+ const client = await pool.connect();
44
+ // A failed ROLLBACK must not replace the caller's error. That error goes
45
+ // to client.release instead: a truthy argument makes pg-pool destroy the
46
+ // connection, so no later caller gets a connection in an unknown
47
+ // transaction state.
48
+ let releaseErr;
49
+ try {
50
+ await client.query("BEGIN");
51
+ const result = await queryFn(client);
52
+ await client.query("COMMIT");
53
+ return result;
54
+ }
55
+ catch (err) {
56
+ try {
57
+ await client.query("ROLLBACK");
58
+ }
59
+ catch (rollbackErr) {
60
+ releaseErr =
61
+ rollbackErr instanceof Error ? rollbackErr : new Error(String(rollbackErr));
62
+ }
63
+ throw err;
64
+ }
65
+ finally {
66
+ client.release(releaseErr);
67
+ }
45
68
  }
46
69
  finally {
47
- client.release();
70
+ this.poolManager.releasePool(tenantSlug);
48
71
  }
49
72
  }
50
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"database-raw.js","sourceRoot":"","sources":["../../src/adapters/database-raw.ts"],"names":[],"mappings":";;;AAGA;;;;;GAKG;AACH,MAAa,kBAAkB;IACA;IAA7B,YAA6B,WAAgC;QAAhC,gBAAW,GAAX,WAAW,CAAqB;IAAG,CAAC;IAEjE;;;OAGG;IACH,KAAK,CAAC,KAAK,CACT,UAAkB,EAClB,IAAY,EACZ,MAAkB;QAElB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,KAAK,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,wBAAwB,CAC5B,UAAkB,EAClB,OAA8C;QAE9C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/B,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;CACF;AA3CD,gDA2CC"}
1
+ {"version":3,"file":"database-raw.js","sourceRoot":"","sources":["../../src/adapters/database-raw.ts"],"names":[],"mappings":";;;AAGA;;;;;;;GAOG;AACH,MAAa,kBAAkB;IACA;IAA7B,YAA6B,WAAgC;QAAhC,gBAAW,GAAX,WAAW,CAAqB;IAAG,CAAC;IAEjE;;;OAGG;IACH,KAAK,CAAC,KAAK,CACT,UAAkB,EAClB,IAAY,EACZ,MAAkB;QAElB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,IAAI,CAAC;gBACH,OAAO,MAAM,MAAM,CAAC,KAAK,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;YAC7C,CAAC;oBAAS,CAAC;gBACT,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,wBAAwB,CAC5B,UAAkB,EAClB,OAA8C;QAE9C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,yEAAyE;YACzE,yEAAyE;YACzE,iEAAiE;YACjE,qBAAqB;YACrB,IAAI,UAA6B,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC5B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;gBACrC,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC7B,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBACjC,CAAC;gBAAC,OAAO,WAAW,EAAE,CAAC;oBACrB,UAAU;wBACR,WAAW,YAAY,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;gBAChF,CAAC;gBACD,MAAM,GAAG,CAAC;YACZ,CAAC;oBAAS,CAAC;gBACT,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;CACF;AA7DD,gDA6DC"}
@@ -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;AAMD,qBAAa,gBAAgB;IAIzB,OAAO,CAAC,IAAI;IAHd,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAW;gBAGjC,IAAI,EAAE,EAAE,CAAC,IAAI,EACrB,OAAO,GAAE,oBAAyB;IAM9B,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,cAAc,EACzD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAM7B;;;;OAIG;IACG,wBAAwB,CAAC,CAAC,EAC9B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACxC,OAAO,CAAC,CAAC,CAAC;CAkCd;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,SAAS,EAAE,MAAM,MAAM,EACvB,OAAO,GAAE,oBAAyB,GACjC,gBAAgB,CAWlB"}