@stratum-hq/mysql 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -31
- package/dist/adapters/shared.d.ts +5 -1
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +9 -1
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/table.d.ts +6 -0
- package/dist/adapters/table.d.ts.map +1 -1
- package/dist/adapters/table.js +26 -11
- package/dist/adapters/table.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/knex.d.ts +11 -1
- package/dist/integrations/knex.d.ts.map +1 -1
- package/dist/integrations/knex.js +148 -10
- package/dist/integrations/knex.js.map +1 -1
- package/dist/integrations/sequelize.d.ts +8 -4
- package/dist/integrations/sequelize.d.ts.map +1 -1
- package/dist/integrations/sequelize.js +8 -4
- package/dist/integrations/sequelize.js.map +1 -1
- package/dist/integrations/typeorm-subscriber.d.ts +57 -1
- package/dist/integrations/typeorm-subscriber.d.ts.map +1 -1
- package/dist/integrations/typeorm-subscriber.js +75 -1
- package/dist/integrations/typeorm-subscriber.js.map +1 -1
- package/dist/pool-manager.d.ts +13 -3
- package/dist/pool-manager.d.ts.map +1 -1
- package/dist/pool-manager.js +84 -37
- package/dist/pool-manager.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/manager.d.ts +5 -6
- package/dist/views/manager.d.ts.map +1 -1
- package/dist/views/manager.js +7 -12
- package/dist/views/manager.js.map +1 -1
- package/package.json +11 -6
package/README.md
CHANGED
|
@@ -43,7 +43,12 @@ await adapter.purgeTenantData("tenant-a");
|
|
|
43
43
|
```typescript
|
|
44
44
|
import { MysqlTableAdapter } from "@stratum-hq/mysql";
|
|
45
45
|
|
|
46
|
-
const adapter = new MysqlTableAdapter({
|
|
46
|
+
const adapter = new MysqlTableAdapter({
|
|
47
|
+
pool,
|
|
48
|
+
databaseName: "myapp",
|
|
49
|
+
// Every base table that has a per-tenant copy. Required by purgeTenantData.
|
|
50
|
+
baseTables: ["users", "orders"],
|
|
51
|
+
});
|
|
47
52
|
|
|
48
53
|
// Returns escaped table name: `users_tenanta`
|
|
49
54
|
const tableName = adapter.scopedTable("tenanta", "users");
|
|
@@ -77,14 +82,19 @@ await adapter.closeAll();
|
|
|
77
82
|
### TypeORM Subscriber (writes only)
|
|
78
83
|
|
|
79
84
|
```typescript
|
|
80
|
-
import {
|
|
85
|
+
import { registerStratumSubscriber } from "@stratum-hq/mysql";
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
});
|
|
87
|
+
await dataSource.initialize();
|
|
88
|
+
// Adds one StratumTypeOrmSubscriber. A second call adds nothing.
|
|
89
|
+
registerStratumSubscriber(dataSource);
|
|
86
90
|
```
|
|
87
91
|
|
|
92
|
+
Call `registerStratumSubscriber()` after `dataSource.initialize()`. It throws before that, because `initialize()` replaces the subscriber list. Do not put `StratumTypeOrmSubscriber` in the `subscribers` option: TypeORM only loads classes decorated with `@EventSubscriber()` from that option.
|
|
93
|
+
|
|
94
|
+
Inserts get the current tenant's `tenant_id`. Updates never change `tenant_id`: `save()` keeps the loaded value, and `update()` / query builder updates drop it from the SET values.
|
|
95
|
+
|
|
96
|
+
Upserts (`repository.upsert()` and `.orUpdate()`) also get the current tenant's `tenant_id` on insert. If the conflict update writes `tenant_id`, the subscriber rejects the statement before it runs. To upsert, leave `tenant_id` out of the entity values and out of the `orUpdate()` columns.
|
|
97
|
+
|
|
88
98
|
**Limitation:** TypeORM subscribers can intercept writes but not reads. Use the shared-table adapter's structured methods for tenant-scoped reads.
|
|
89
99
|
|
|
90
100
|
### Knex Helper
|
|
@@ -93,49 +103,35 @@ const dataSource = new DataSource({
|
|
|
93
103
|
import { withTenantScope } from "@stratum-hq/mysql";
|
|
94
104
|
|
|
95
105
|
const tenantKnex = withTenantScope(knex, "tenant-a");
|
|
96
|
-
const users = await tenantKnex("users").
|
|
97
|
-
//
|
|
106
|
+
const users = await tenantKnex("users").where("name", "like", q).orWhere("email", "like", q);
|
|
107
|
+
// Compiles to: WHERE tenant_id = 'tenant-a' AND (name LIKE ? OR email LIKE ?)
|
|
98
108
|
```
|
|
99
109
|
|
|
110
|
+
Your where clauses are always grouped after the tenant filter, including on clones and when the builder is used as a subquery. `insert()` sets `tenant_id`, `update()` never changes it, and `onConflict().merge()`, `upsert()` and `truncate()` throw.
|
|
111
|
+
|
|
100
112
|
### Sequelize Adapter
|
|
101
113
|
|
|
102
114
|
```typescript
|
|
103
115
|
import { withMysqlTenantScope } from "@stratum-hq/mysql";
|
|
104
116
|
|
|
105
|
-
await withMysqlTenantScope(sequelize, "tenant-a", async (scoped) => {
|
|
106
|
-
//
|
|
117
|
+
await withMysqlTenantScope(sequelize, "tenant-a", async (scoped, transaction) => {
|
|
118
|
+
// @stratum_tenant_id is set on the transaction's connection only.
|
|
119
|
+
// Pass the transaction to every query, or it runs on another connection.
|
|
107
120
|
// Guaranteed cleanup via try/finally, even on errors
|
|
108
|
-
const
|
|
121
|
+
const [rows] = await scoped.query("SELECT @stratum_tenant_id", { transaction });
|
|
109
122
|
});
|
|
110
123
|
```
|
|
111
124
|
|
|
112
|
-
## MySQL Views (
|
|
113
|
-
|
|
114
|
-
Views provide a convenient way to create tenant-scoped read access using MySQL session variables.
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
import { createTenantView, setTenantSession } from "@stratum-hq/mysql";
|
|
118
|
-
|
|
119
|
-
// Create a view that filters by @stratum_tenant_id
|
|
120
|
-
await createTenantView(pool, "users");
|
|
121
|
-
// Creates: CREATE OR REPLACE VIEW users_tenant_view AS
|
|
122
|
-
// SELECT * FROM users WHERE tenant_id = @stratum_tenant_id
|
|
123
|
-
|
|
124
|
-
// Set the session variable, then query the view
|
|
125
|
-
const conn = await pool.getConnection();
|
|
126
|
-
await setTenantSession(conn, "tenant-a");
|
|
127
|
-
const [rows] = await conn.query("SELECT * FROM users_tenant_view");
|
|
128
|
-
conn.release();
|
|
129
|
-
```
|
|
125
|
+
## MySQL Views (not supported)
|
|
130
126
|
|
|
131
|
-
|
|
127
|
+
`createTenantView()` is deprecated and always throws. MySQL does not allow a view to read a session variable (`ER_VIEW_SELECT_VARIABLE`), so a view filtered on `@stratum_tenant_id` cannot be created. Use the shared-table adapter's scoped methods. `setTenantSession()` and `dropTenantView()` remain available.
|
|
132
128
|
|
|
133
129
|
## GDPR Compliance
|
|
134
130
|
|
|
135
131
|
All three adapters implement `purgeTenantData(tenantSlug)`:
|
|
136
132
|
|
|
137
133
|
- **Shared table**: discovers tenant tables via `INFORMATION_SCHEMA`, then `DELETE FROM table WHERE tenant_id = ?`
|
|
138
|
-
- **Table-per-tenant**:
|
|
134
|
+
- **Table-per-tenant**: `DROP TABLE` for exactly `{base}_{slug}` for each entry in the `baseTables` option, which it requires
|
|
139
135
|
- **Database-per-tenant**: `DROP DATABASE stratum_tenant_slug`
|
|
140
136
|
|
|
141
137
|
## License
|
|
@@ -14,7 +14,11 @@ export declare class MysqlSharedAdapter implements MysqlAdapter {
|
|
|
14
14
|
scopedSelect(tenantId: string, table: string, conditions?: Record<string, unknown>, columns?: string[]): Promise<unknown[]>;
|
|
15
15
|
/** INSERT with tenant_id added to data. */
|
|
16
16
|
scopedInsert(tenantId: string, table: string, data: Record<string, unknown>): Promise<unknown>;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* UPDATE SET ... WHERE tenant_id = ? AND conditions.
|
|
19
|
+
* tenant_id (in any letter case) is dropped from data, so an update can never
|
|
20
|
+
* move a row to another tenant.
|
|
21
|
+
*/
|
|
18
22
|
scopedUpdate(tenantId: string, table: string, data: Record<string, unknown>, conditions: Record<string, unknown>): Promise<unknown>;
|
|
19
23
|
/** DELETE WHERE tenant_id = ? AND optional conditions. */
|
|
20
24
|
scopedDelete(tenantId: string, table: string, conditions?: Record<string, unknown>): Promise<unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/adapters/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,yBAAyB,EAEzB,WAAW,EACX,YAAY,EACb,MAAM,aAAa,CAAC;AAGrB;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,OAAO,EAAE,yBAAyB;IAK9C;;;OAGG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,GAAE,MAAM,EAAU,GACxB,OAAO,CAAC,OAAO,EAAE,CAAC;IAqBrB,2CAA2C;IACrC,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC;IAanB
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/adapters/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,yBAAyB,EAEzB,WAAW,EACX,YAAY,EACb,MAAM,aAAa,CAAC;AAGrB;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,OAAO,EAAE,yBAAyB;IAK9C;;;OAGG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,GAAE,MAAM,EAAU,GACxB,OAAO,CAAC,OAAO,EAAE,CAAC;IAqBrB,2CAA2C;IACrC,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC;IAanB;;;;OAIG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,OAAO,CAAC;IA4BnB,0DAA0D;IACpD,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,OAAO,CAAC,OAAO,CAAC;IAkBnB;;;;;;OAMG;IACG,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAInE,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAuB7D,kCAAkC;IAClC,QAAQ,IAAI,YAAY;CAGzB"}
|
package/dist/adapters/shared.js
CHANGED
|
@@ -44,9 +44,17 @@ class MysqlSharedAdapter {
|
|
|
44
44
|
const sql = `INSERT INTO ${qualifiedTable} (${cols}) VALUES (${placeholders})`;
|
|
45
45
|
return this.pool.query(sql, values);
|
|
46
46
|
}
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* UPDATE SET ... WHERE tenant_id = ? AND conditions.
|
|
49
|
+
* tenant_id (in any letter case) is dropped from data, so an update can never
|
|
50
|
+
* move a row to another tenant.
|
|
51
|
+
*/
|
|
48
52
|
async scopedUpdate(tenantId, table, data, conditions) {
|
|
49
53
|
(0, utils_js_1.assertTenantId)(tenantId);
|
|
54
|
+
data = Object.fromEntries(Object.entries(data).filter(([col]) => col.toLowerCase() !== "tenant_id"));
|
|
55
|
+
if (Object.keys(data).length === 0) {
|
|
56
|
+
throw new Error("scopedUpdate: data has no columns to update other than tenant_id");
|
|
57
|
+
}
|
|
50
58
|
const qualifiedTable = `${(0, utils_js_1.escapeIdentifier)(this.databaseName)}.${(0, utils_js_1.escapeIdentifier)(table)}`;
|
|
51
59
|
const setClauses = Object.keys(data)
|
|
52
60
|
.map((col) => `${(0, utils_js_1.escapeIdentifier)(col)} = ?`)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/adapters/shared.ts"],"names":[],"mappings":";;;AAOA,0CAAsF;AAEtF;;;GAGG;AACH,MAAa,kBAAkB;IACZ,IAAI,CAAgB;IACpB,YAAY,CAAS;IAEtC,YAAY,OAAkC;QAC5C,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAC3C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,YAAY,CAChB,QAAgB,EAChB,KAAa,EACb,UAAoC,EACpC,UAAoB,CAAC,GAAG,CAAC;QAEzB,IAAA,yBAAc,EAAC,QAAQ,CAAC,CAAC;QAEzB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAA,2BAAgB,EAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpF,MAAM,cAAc,GAAG,GAAG,IAAA,2BAAgB,EAAC,IAAI,CAAC,YAAY,CAAC,IAAI,IAAA,2BAAgB,EAAC,KAAK,CAAC,EAAE,CAAC;QAE3F,MAAM,YAAY,GAAa,CAAC,eAAe,CAAC,CAAC;QACjD,MAAM,MAAM,GAAc,CAAC,QAAQ,CAAC,CAAC;QAErC,IAAI,UAAU,EAAE,CAAC;YACf,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBACpD,YAAY,CAAC,IAAI,CAAC,GAAG,IAAA,2BAAgB,EAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAClD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QAED,MAAM,GAAG,GAAG,UAAU,IAAI,SAAS,cAAc,UAAU,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACxF,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAyB,CAAC;QAC1E,OAAO,IAAI,CAAC;IACd,CAAC;IAED,2CAA2C;IAC3C,KAAK,CAAC,YAAY,CAChB,QAAgB,EAChB,KAAa,EACb,IAA6B;QAE7B,IAAA,yBAAc,EAAC,QAAQ,CAAC,CAAC;QAEzB,MAAM,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;QAC7C,MAAM,cAAc,GAAG,GAAG,IAAA,2BAAgB,EAAC,IAAI,CAAC,YAAY,CAAC,IAAI,IAAA,2BAAgB,EAAC,KAAK,CAAC,EAAE,CAAC;QAC3F,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,2BAAgB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/D,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAElC,MAAM,GAAG,GAAG,eAAe,cAAc,KAAK,IAAI,aAAa,YAAY,GAAG,CAAC;QAC/E,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/adapters/shared.ts"],"names":[],"mappings":"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"}
|
package/dist/adapters/table.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { MysqlAdapter, MysqlTableAdapterOptions, MysqlPoolLike, PurgeResult
|
|
|
8
8
|
export declare class MysqlTableAdapter implements MysqlAdapter {
|
|
9
9
|
private readonly pool;
|
|
10
10
|
private readonly databaseName;
|
|
11
|
+
private readonly baseTables;
|
|
11
12
|
constructor(options: MysqlTableAdapterOptions);
|
|
12
13
|
/**
|
|
13
14
|
* Validates slug and returns the escaped tenant-scoped table name
|
|
@@ -16,6 +17,11 @@ export declare class MysqlTableAdapter implements MysqlAdapter {
|
|
|
16
17
|
scopedTable(tenantSlug: string, baseTableName: string): string;
|
|
17
18
|
/** Returns the underlying pool for raw queries against tenant tables. */
|
|
18
19
|
getPool(): MysqlPoolLike;
|
|
20
|
+
/**
|
|
21
|
+
* Drops `{base}_{tenantSlug}` for every base in the baseTables option.
|
|
22
|
+
* Throws when baseTables is not set: discovering tables by name suffix would
|
|
23
|
+
* also match other tenants whose slug ends with this one.
|
|
24
|
+
*/
|
|
19
25
|
purgeTenantData(tenantSlug: string): Promise<PurgeResult>;
|
|
20
26
|
/** Returns adapter statistics. */
|
|
21
27
|
getStats(): AdapterStats;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/adapters/table.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,aAAa,EACb,WAAW,EACX,YAAY,EACb,MAAM,aAAa,CAAC;AAGrB;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/adapters/table.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,aAAa,EACb,WAAW,EACX,YAAY,EACb,MAAM,aAAa,CAAC;AAGrB;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;gBAEtC,OAAO,EAAE,wBAAwB;IAkB7C;;;OAGG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM;IAS9D,yEAAyE;IACzE,OAAO,IAAI,aAAa;IAIxB;;;;OAIG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAgC/D,kCAAkC;IAClC,QAAQ,IAAI,YAAY;CAGzB"}
|
package/dist/adapters/table.js
CHANGED
|
@@ -12,9 +12,21 @@ const utils_js_1 = require("../utils.js");
|
|
|
12
12
|
class MysqlTableAdapter {
|
|
13
13
|
pool;
|
|
14
14
|
databaseName;
|
|
15
|
+
baseTables;
|
|
15
16
|
constructor(options) {
|
|
16
17
|
this.pool = options.pool;
|
|
17
18
|
this.databaseName = options.databaseName;
|
|
19
|
+
if (options.baseTables) {
|
|
20
|
+
for (const a of options.baseTables) {
|
|
21
|
+
for (const b of options.baseTables) {
|
|
22
|
+
if (b.toLowerCase().startsWith(`${a.toLowerCase()}_`)) {
|
|
23
|
+
throw new Error(`MysqlTableAdapter: base tables "${a}" and "${b}" are ambiguous, ` +
|
|
24
|
+
`because "${b}_{slug}" can also be read as "${a}_{slug}"`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
this.baseTables = [...options.baseTables];
|
|
29
|
+
}
|
|
18
30
|
}
|
|
19
31
|
/**
|
|
20
32
|
* Validates slug and returns the escaped tenant-scoped table name
|
|
@@ -22,6 +34,9 @@ class MysqlTableAdapter {
|
|
|
22
34
|
*/
|
|
23
35
|
scopedTable(tenantSlug, baseTableName) {
|
|
24
36
|
(0, core_1.validateSlug)(tenantSlug);
|
|
37
|
+
if (this.baseTables && !this.baseTables.includes(baseTableName)) {
|
|
38
|
+
throw new Error(`MysqlTableAdapter: "${baseTableName}" is not in baseTables`);
|
|
39
|
+
}
|
|
25
40
|
const tableName = `${baseTableName}_${tenantSlug}`;
|
|
26
41
|
return (0, utils_js_1.escapeIdentifier)(tableName);
|
|
27
42
|
}
|
|
@@ -29,23 +44,23 @@ class MysqlTableAdapter {
|
|
|
29
44
|
getPool() {
|
|
30
45
|
return this.pool;
|
|
31
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Drops `{base}_{tenantSlug}` for every base in the baseTables option.
|
|
49
|
+
* Throws when baseTables is not set: discovering tables by name suffix would
|
|
50
|
+
* also match other tenants whose slug ends with this one.
|
|
51
|
+
*/
|
|
32
52
|
async purgeTenantData(tenantSlug) {
|
|
33
53
|
(0, utils_js_1.assertTenantId)(tenantSlug);
|
|
34
54
|
(0, core_1.validateSlug)(tenantSlug);
|
|
55
|
+
if (!this.baseTables) {
|
|
56
|
+
throw new Error("MysqlTableAdapter: purgeTenantData requires the baseTables option, " +
|
|
57
|
+
"listing every base table name that has a per-tenant copy");
|
|
58
|
+
}
|
|
35
59
|
const escapedDb = (0, utils_js_1.escapeIdentifier)(this.databaseName);
|
|
36
|
-
// SHOW TABLES LIKE '%_tenantSlug' to find all tenant tables.
|
|
37
|
-
const pattern = `%_${tenantSlug}`;
|
|
38
|
-
const [tableRows] = await this.pool.query(`SHOW TABLES FROM ${escapedDb} LIKE ?`, [pattern]);
|
|
39
60
|
const errors = [];
|
|
40
61
|
let tablesProcessed = 0;
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const tableName = Object.values(row)[0];
|
|
44
|
-
if (!tableName)
|
|
45
|
-
continue;
|
|
46
|
-
// Post-filter: verify exact suffix match to prevent 'e' matching 'orders_acme'
|
|
47
|
-
if (!tableName.endsWith(exactSuffix))
|
|
48
|
-
continue;
|
|
62
|
+
for (const base of this.baseTables) {
|
|
63
|
+
const tableName = `${base}_${tenantSlug}`;
|
|
49
64
|
try {
|
|
50
65
|
await this.pool.query(`DROP TABLE IF EXISTS ${escapedDb}.${(0, utils_js_1.escapeIdentifier)(tableName)}`);
|
|
51
66
|
tablesProcessed++;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/adapters/table.ts"],"names":[],"mappings":";;;AAAA,2CAAgD;AAQhD,0CAA+D;AAE/D;;;;;GAKG;AACH,MAAa,iBAAiB;IACX,IAAI,CAAgB;IACpB,YAAY,CAAS;
|
|
1
|
+
{"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/adapters/table.ts"],"names":[],"mappings":";;;AAAA,2CAAgD;AAQhD,0CAA+D;AAE/D;;;;;GAKG;AACH,MAAa,iBAAiB;IACX,IAAI,CAAgB;IACpB,YAAY,CAAS;IACrB,UAAU,CAAuB;IAElD,YAAY,OAAiC;QAC3C,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACzC,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACvB,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;gBACnC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;oBACnC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC;wBACtD,MAAM,IAAI,KAAK,CACb,mCAAmC,CAAC,UAAU,CAAC,mBAAmB;4BAChE,YAAY,CAAC,iCAAiC,CAAC,UAAU,CAC5D,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,UAAkB,EAAE,aAAqB;QACnD,IAAA,mBAAY,EAAC,UAAU,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,uBAAuB,aAAa,wBAAwB,CAAC,CAAC;QAChF,CAAC;QACD,MAAM,SAAS,GAAG,GAAG,aAAa,IAAI,UAAU,EAAE,CAAC;QACnD,OAAO,IAAA,2BAAgB,EAAC,SAAS,CAAC,CAAC;IACrC,CAAC;IAED,yEAAyE;IACzE,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,eAAe,CAAC,UAAkB;QACtC,IAAA,yBAAc,EAAC,UAAU,CAAC,CAAC;QAC3B,IAAA,mBAAY,EAAC,UAAU,CAAC,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,qEAAqE;gBACnE,0DAA0D,CAC7D,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,IAAA,2BAAgB,EAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACtD,MAAM,MAAM,GAA0B,EAAE,CAAC;QACzC,IAAI,eAAe,GAAG,CAAC,CAAC;QAExB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACnC,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,UAAU,EAAE,CAAC;YAC1C,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,wBAAwB,SAAS,IAAI,IAAA,2BAAgB,EAAC,SAAS,CAAC,EAAE,CAAC,CAAC;gBAC1F,eAAe,EAAE,CAAC;YACpB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,GAAY,EAAE,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;QAED,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;YAC5B,eAAe;YACf,WAAW,EAAE,CAAC;YACd,MAAM;SACP,CAAC;IACJ,CAAC;IAED,kCAAkC;IAClC,QAAQ;QACN,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC1C,CAAC;CACF;AAlFD,8CAkFC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ export { MysqlTableAdapter } from "./adapters/table.js";
|
|
|
4
4
|
export { MysqlDatabaseAdapter } from "./adapters/database.js";
|
|
5
5
|
export { MysqlPoolManager } from "./pool-manager.js";
|
|
6
6
|
export { createTenantView, dropTenantView, setTenantSession } from "./views/manager.js";
|
|
7
|
-
export { StratumTypeOrmSubscriber } from "./integrations/typeorm-subscriber.js";
|
|
8
|
-
export type { InsertEvent, EntitySubscriberInterface } from "./integrations/typeorm-subscriber.js";
|
|
7
|
+
export { StratumTypeOrmSubscriber, registerStratumSubscriber, } from "./integrations/typeorm-subscriber.js";
|
|
8
|
+
export type { InsertEvent, UpdateEvent, BeforeQueryEvent, EntitySubscriberInterface, TypeOrmDataSourceLike, } from "./integrations/typeorm-subscriber.js";
|
|
9
9
|
export { withTenantScope } from "./integrations/knex.js";
|
|
10
10
|
export type { KnexLike, KnexQueryBuilderLike } from "./integrations/knex.js";
|
|
11
11
|
export { withMysqlTenantScope } from "./integrations/sequelize.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGxF,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGxF,OAAO,EACL,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGjE,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.aggregatePurgeResults = exports.escapeIdentifier = exports.assertTenantId = exports.withMysqlTenantScope = exports.withTenantScope = exports.StratumTypeOrmSubscriber = exports.setTenantSession = exports.dropTenantView = exports.createTenantView = exports.MysqlPoolManager = exports.MysqlDatabaseAdapter = exports.MysqlTableAdapter = exports.MysqlSharedAdapter = void 0;
|
|
3
|
+
exports.aggregatePurgeResults = exports.escapeIdentifier = exports.assertTenantId = exports.withMysqlTenantScope = exports.withTenantScope = exports.registerStratumSubscriber = exports.StratumTypeOrmSubscriber = exports.setTenantSession = exports.dropTenantView = exports.createTenantView = exports.MysqlPoolManager = exports.MysqlDatabaseAdapter = exports.MysqlTableAdapter = exports.MysqlSharedAdapter = void 0;
|
|
4
4
|
// Adapters
|
|
5
5
|
var shared_js_1 = require("./adapters/shared.js");
|
|
6
6
|
Object.defineProperty(exports, "MysqlSharedAdapter", { enumerable: true, get: function () { return shared_js_1.MysqlSharedAdapter; } });
|
|
@@ -19,6 +19,7 @@ Object.defineProperty(exports, "setTenantSession", { enumerable: true, get: func
|
|
|
19
19
|
// Integrations
|
|
20
20
|
var typeorm_subscriber_js_1 = require("./integrations/typeorm-subscriber.js");
|
|
21
21
|
Object.defineProperty(exports, "StratumTypeOrmSubscriber", { enumerable: true, get: function () { return typeorm_subscriber_js_1.StratumTypeOrmSubscriber; } });
|
|
22
|
+
Object.defineProperty(exports, "registerStratumSubscriber", { enumerable: true, get: function () { return typeorm_subscriber_js_1.registerStratumSubscriber; } });
|
|
22
23
|
var knex_js_1 = require("./integrations/knex.js");
|
|
23
24
|
Object.defineProperty(exports, "withTenantScope", { enumerable: true, get: function () { return knex_js_1.withTenantScope; } });
|
|
24
25
|
var sequelize_js_1 = require("./integrations/sequelize.js");
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAaA,WAAW;AACX,kDAA0D;AAAjD,+GAAA,kBAAkB,OAAA;AAC3B,gDAAwD;AAA/C,6GAAA,iBAAiB,OAAA;AAC1B,sDAA8D;AAArD,mHAAA,oBAAoB,OAAA;AAE7B,eAAe;AACf,qDAAqD;AAA5C,mHAAA,gBAAgB,OAAA;AAEzB,QAAQ;AACR,iDAAwF;AAA/E,8GAAA,gBAAgB,OAAA;AAAE,4GAAA,cAAc,OAAA;AAAE,8GAAA,gBAAgB,OAAA;AAE3D,eAAe;AACf,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAaA,WAAW;AACX,kDAA0D;AAAjD,+GAAA,kBAAkB,OAAA;AAC3B,gDAAwD;AAA/C,6GAAA,iBAAiB,OAAA;AAC1B,sDAA8D;AAArD,mHAAA,oBAAoB,OAAA;AAE7B,eAAe;AACf,qDAAqD;AAA5C,mHAAA,gBAAgB,OAAA;AAEzB,QAAQ;AACR,iDAAwF;AAA/E,8GAAA,gBAAgB,OAAA;AAAE,4GAAA,cAAc,OAAA;AAAE,8GAAA,gBAAgB,OAAA;AAE3D,eAAe;AACf,8EAG8C;AAF5C,iIAAA,wBAAwB,OAAA;AACxB,kIAAA,yBAAyB,OAAA;AAS3B,kDAAyD;AAAhD,0GAAA,eAAe,OAAA;AAExB,4DAAmE;AAA1D,oHAAA,oBAAoB,OAAA;AAG7B,YAAY;AACZ,uCAAqF;AAA5E,0GAAA,cAAc,OAAA;AAAE,4GAAA,gBAAgB,OAAA;AAAE,iHAAA,qBAAqB,OAAA"}
|
|
@@ -11,11 +11,21 @@ export interface KnexLike {
|
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Returns a wrapper function that pre-scopes queries to the given tenant.
|
|
14
|
-
* Call the returned function with a table name to get a
|
|
14
|
+
* Call the returned function with a table name to get a builder whose query
|
|
15
|
+
* always compiles to `WHERE tenant_id = ? AND (<your where clauses>)`.
|
|
16
|
+
*
|
|
17
|
+
* The caller's where clauses (including orWhere, whereRaw and clearWhere) are
|
|
18
|
+
* kept together in one parenthesized group, so they cannot widen the query
|
|
19
|
+
* past the tenant. This also holds for clones and for the builder used as a
|
|
20
|
+
* subquery.
|
|
15
21
|
*
|
|
16
22
|
* INSERT operations automatically inject tenant_id into the data object, so
|
|
17
23
|
* you do not need to include it yourself:
|
|
18
24
|
* await scoped("users").insert({ name: "Alice" });
|
|
25
|
+
*
|
|
26
|
+
* UPDATE never changes tenant_id: the column is dropped from the update data.
|
|
27
|
+
* onConflict().merge(), upsert() and truncate() throw, because MySQL applies
|
|
28
|
+
* none of them through the WHERE clause. onConflict().ignore() is allowed.
|
|
19
29
|
*/
|
|
20
30
|
export declare function withTenantScope(knex: KnexLike, tenantId: string): (tableName: string) => KnexQueryBuilderLike;
|
|
21
31
|
//# sourceMappingURL=knex.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knex.d.ts","sourceRoot":"","sources":["../../src/integrations/knex.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,MAAM,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpF,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5C,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,CAAC;CAC3C;
|
|
1
|
+
{"version":3,"file":"knex.d.ts","sourceRoot":"","sources":["../../src/integrations/knex.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,MAAM,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpF,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5C,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,CAAC;CAC3C;AA8BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,MAAM,GACf,CAAC,SAAS,EAAE,MAAM,KAAK,oBAAoB,CAwI7C"}
|
|
@@ -2,24 +2,162 @@
|
|
|
2
2
|
// ─── Structural type for Knex (no hard dependency on knex) ───
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.withTenantScope = withTenantScope;
|
|
5
|
+
const TENANT_COLUMN = "tenant_id";
|
|
6
|
+
/** Builder methods that would write or destroy rows without the tenant filter. */
|
|
7
|
+
const REFUSED_METHODS = new Set(["upsert", "truncate"]);
|
|
8
|
+
function isTenantColumn(name) {
|
|
9
|
+
return name.toLowerCase() === TENANT_COLUMN;
|
|
10
|
+
}
|
|
11
|
+
function withoutTenantColumn(row) {
|
|
12
|
+
const copy = {};
|
|
13
|
+
for (const [key, value] of Object.entries(row)) {
|
|
14
|
+
if (!isTenantColumn(key))
|
|
15
|
+
copy[key] = value;
|
|
16
|
+
}
|
|
17
|
+
return copy;
|
|
18
|
+
}
|
|
5
19
|
/**
|
|
6
20
|
* Returns a wrapper function that pre-scopes queries to the given tenant.
|
|
7
|
-
* Call the returned function with a table name to get a
|
|
21
|
+
* Call the returned function with a table name to get a builder whose query
|
|
22
|
+
* always compiles to `WHERE tenant_id = ? AND (<your where clauses>)`.
|
|
23
|
+
*
|
|
24
|
+
* The caller's where clauses (including orWhere, whereRaw and clearWhere) are
|
|
25
|
+
* kept together in one parenthesized group, so they cannot widen the query
|
|
26
|
+
* past the tenant. This also holds for clones and for the builder used as a
|
|
27
|
+
* subquery.
|
|
8
28
|
*
|
|
9
29
|
* INSERT operations automatically inject tenant_id into the data object, so
|
|
10
30
|
* you do not need to include it yourself:
|
|
11
31
|
* await scoped("users").insert({ name: "Alice" });
|
|
32
|
+
*
|
|
33
|
+
* UPDATE never changes tenant_id: the column is dropped from the update data.
|
|
34
|
+
* onConflict().merge(), upsert() and truncate() throw, because MySQL applies
|
|
35
|
+
* none of them through the WHERE clause. onConflict().ignore() is allowed.
|
|
12
36
|
*/
|
|
13
37
|
function withTenantScope(knex, tenantId) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
38
|
+
// Statement objects this module created, so they can be recognized again on
|
|
39
|
+
// clones, which copy the statement array but share the statement objects.
|
|
40
|
+
const tenantStatements = new WeakSet();
|
|
41
|
+
const groupedStatements = new WeakMap();
|
|
42
|
+
function normalize(builder) {
|
|
43
|
+
const statements = builder._statements;
|
|
44
|
+
const others = [];
|
|
45
|
+
let userWheres = [];
|
|
46
|
+
for (const stmt of statements) {
|
|
47
|
+
if (stmt.grouping !== "where") {
|
|
48
|
+
others.push(stmt);
|
|
49
|
+
}
|
|
50
|
+
else if (tenantStatements.has(stmt)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
else if (groupedStatements.has(stmt)) {
|
|
54
|
+
userWheres = [...userWheres, ...groupedStatements.get(stmt)];
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
userWheres = [...userWheres, stmt];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const tenantStmt = {
|
|
61
|
+
grouping: "where",
|
|
62
|
+
type: "whereBasic",
|
|
63
|
+
column: TENANT_COLUMN,
|
|
64
|
+
operator: "=",
|
|
65
|
+
value: tenantId,
|
|
66
|
+
not: false,
|
|
67
|
+
bool: "and",
|
|
68
|
+
asColumn: false,
|
|
21
69
|
};
|
|
22
|
-
|
|
23
|
-
|
|
70
|
+
tenantStatements.add(tenantStmt);
|
|
71
|
+
const next = [...others, tenantStmt];
|
|
72
|
+
if (userWheres.length > 0) {
|
|
73
|
+
const captured = userWheres;
|
|
74
|
+
const groupStmt = {
|
|
75
|
+
grouping: "where",
|
|
76
|
+
type: "whereWrapped",
|
|
77
|
+
value: function () {
|
|
78
|
+
this._statements.push(...captured);
|
|
79
|
+
},
|
|
80
|
+
not: false,
|
|
81
|
+
bool: "and",
|
|
82
|
+
};
|
|
83
|
+
groupedStatements.set(groupStmt, captured);
|
|
84
|
+
next.push(groupStmt);
|
|
85
|
+
}
|
|
86
|
+
builder._statements = next;
|
|
87
|
+
}
|
|
88
|
+
function scope(target) {
|
|
89
|
+
// Knex methods call other methods on `this`. Only the outermost call is
|
|
90
|
+
// intercepted and normalized, so a statement array is never swapped out
|
|
91
|
+
// while a Knex method is still writing to it.
|
|
92
|
+
let depth = 0;
|
|
93
|
+
const proxy = new Proxy(target, {
|
|
94
|
+
get(obj, prop, receiver) {
|
|
95
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
96
|
+
if (typeof value !== "function" || typeof prop !== "string" || prop === "constructor") {
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
if (REFUSED_METHODS.has(prop)) {
|
|
100
|
+
return () => {
|
|
101
|
+
throw new Error(`withTenantScope: ${prop}() is not allowed on a tenant-scoped builder, ` +
|
|
102
|
+
`because MySQL does not apply the tenant filter to it`);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (depth > 0) {
|
|
106
|
+
return (...args) => value.apply(proxy, args);
|
|
107
|
+
}
|
|
108
|
+
if (prop === "onConflict") {
|
|
109
|
+
return (...args) => {
|
|
110
|
+
const onConflict = value.apply(proxy, args);
|
|
111
|
+
return {
|
|
112
|
+
ignore: () => onConflict.ignore(),
|
|
113
|
+
merge: () => {
|
|
114
|
+
throw new Error("withTenantScope: onConflict().merge() is not allowed on a tenant-scoped builder, " +
|
|
115
|
+
"because MySQL does not apply the tenant filter to it");
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return (...args) => {
|
|
121
|
+
if (prop === "insert") {
|
|
122
|
+
const data = args[0];
|
|
123
|
+
const inject = (row) => ({ ...row, tenant_id: tenantId });
|
|
124
|
+
args[0] = Array.isArray(data) ? data.map(inject) : inject(data);
|
|
125
|
+
}
|
|
126
|
+
else if (prop === "update") {
|
|
127
|
+
if (typeof args[0] === "string") {
|
|
128
|
+
if (isTenantColumn(args[0])) {
|
|
129
|
+
throw new Error(`withTenantScope: update() cannot change ${TENANT_COLUMN}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (args[0] && typeof args[0] === "object") {
|
|
133
|
+
args[0] = withoutTenantColumn(args[0]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else if (prop === "increment" || prop === "decrement") {
|
|
137
|
+
const cols = typeof args[0] === "string" ? [args[0]] : Object.keys((args[0] ?? {}));
|
|
138
|
+
if (cols.some(isTenantColumn)) {
|
|
139
|
+
throw new Error(`withTenantScope: ${prop}() cannot change ${TENANT_COLUMN}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
normalize(obj);
|
|
143
|
+
let result;
|
|
144
|
+
depth++;
|
|
145
|
+
try {
|
|
146
|
+
result = value.apply(proxy, args);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
depth--;
|
|
150
|
+
}
|
|
151
|
+
normalize(obj);
|
|
152
|
+
if (prop === "clone")
|
|
153
|
+
return scope(result);
|
|
154
|
+
return result === obj ? proxy : result;
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
normalize(target);
|
|
159
|
+
return proxy;
|
|
160
|
+
}
|
|
161
|
+
return (tableName) => scope(knex(tableName));
|
|
24
162
|
}
|
|
25
163
|
//# sourceMappingURL=knex.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knex.js","sourceRoot":"","sources":["../../src/integrations/knex.ts"],"names":[],"mappings":";AAAA,gEAAgE;;
|
|
1
|
+
{"version":3,"file":"knex.js","sourceRoot":"","sources":["../../src/integrations/knex.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,9 +4,13 @@ export interface SequelizeLike {
|
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
6
|
* Runs fn inside a session-variable scope for MySQL tenant isolation.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Sets @stratum_tenant_id inside a Sequelize transaction and clears it in a
|
|
8
|
+
* finally block, even if fn throws.
|
|
9
|
+
*
|
|
10
|
+
* The variable exists only on the transaction's connection. fn receives that
|
|
11
|
+
* transaction as its second argument, and every query inside fn must pass it
|
|
12
|
+
* (`{ transaction }`); a query without it runs on another pooled connection
|
|
13
|
+
* where the variable is not set.
|
|
10
14
|
*/
|
|
11
|
-
export declare function withMysqlTenantScope<T>(sequelize: SequelizeLike, tenantId: string, fn: (sequelize: SequelizeLike) => Promise<T>): Promise<T>;
|
|
15
|
+
export declare function withMysqlTenantScope<T>(sequelize: SequelizeLike, tenantId: string, fn: (sequelize: SequelizeLike, transaction: unknown) => Promise<T>): Promise<T>;
|
|
12
16
|
//# sourceMappingURL=sequelize.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequelize.d.ts","sourceRoot":"","sources":["../../src/integrations/sequelize.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC5D;AAED
|
|
1
|
+
{"version":3,"file":"sequelize.d.ts","sourceRoot":"","sources":["../../src/integrations/sequelize.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC5D;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,SAAS,EAAE,aAAa,EACxB,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GACjE,OAAO,CAAC,CAAC,CAAC,CAYZ"}
|
|
@@ -4,9 +4,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.withMysqlTenantScope = withMysqlTenantScope;
|
|
5
5
|
/**
|
|
6
6
|
* Runs fn inside a session-variable scope for MySQL tenant isolation.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Sets @stratum_tenant_id inside a Sequelize transaction and clears it in a
|
|
8
|
+
* finally block, even if fn throws.
|
|
9
|
+
*
|
|
10
|
+
* The variable exists only on the transaction's connection. fn receives that
|
|
11
|
+
* transaction as its second argument, and every query inside fn must pass it
|
|
12
|
+
* (`{ transaction }`); a query without it runs on another pooled connection
|
|
13
|
+
* where the variable is not set.
|
|
10
14
|
*/
|
|
11
15
|
async function withMysqlTenantScope(sequelize, tenantId, fn) {
|
|
12
16
|
return sequelize.transaction(async (transaction) => {
|
|
@@ -15,7 +19,7 @@ async function withMysqlTenantScope(sequelize, tenantId, fn) {
|
|
|
15
19
|
transaction,
|
|
16
20
|
});
|
|
17
21
|
try {
|
|
18
|
-
return await fn(sequelize);
|
|
22
|
+
return await fn(sequelize, transaction);
|
|
19
23
|
}
|
|
20
24
|
finally {
|
|
21
25
|
await sequelize.query("SET @stratum_tenant_id = NULL", { transaction });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequelize.js","sourceRoot":"","sources":["../../src/integrations/sequelize.ts"],"names":[],"mappings":";AAAA,6DAA6D;;
|
|
1
|
+
{"version":3,"file":"sequelize.js","sourceRoot":"","sources":["../../src/integrations/sequelize.ts"],"names":[],"mappings":";AAAA,6DAA6D;;AAiB7D,oDAgBC;AA1BD;;;;;;;;;GASG;AACI,KAAK,UAAU,oBAAoB,CACxC,SAAwB,EACxB,QAAgB,EAChB,EAAkE;IAElE,OAAO,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE;QACjD,MAAM,SAAS,CAAC,KAAK,CAAC,4BAA4B,EAAE;YAClD,YAAY,EAAE,CAAC,QAAQ,CAAC;YACxB,WAAW;SACZ,CAAC,CAAC;QACH,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAC1C,CAAC;gBAAS,CAAC;YACT,MAAM,SAAS,CAAC,KAAK,CAAC,+BAA+B,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
export interface InsertEvent {
|
|
2
2
|
entity: Record<string, unknown>;
|
|
3
3
|
}
|
|
4
|
+
export interface UpdateEvent {
|
|
5
|
+
/** The entity being saved, or the SET values of a query builder / repository update. */
|
|
6
|
+
entity?: Record<string, unknown> | null;
|
|
7
|
+
/** The row as loaded from the database, when TypeORM has it (save()). */
|
|
8
|
+
databaseEntity?: Record<string, unknown> | null;
|
|
9
|
+
metadata?: {
|
|
10
|
+
columns: {
|
|
11
|
+
propertyName: string;
|
|
12
|
+
databaseName: string;
|
|
13
|
+
}[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface BeforeQueryEvent {
|
|
17
|
+
/** The SQL text that TypeORM is about to send to MySQL. */
|
|
18
|
+
query: string;
|
|
19
|
+
}
|
|
4
20
|
export interface EntitySubscriberInterface {
|
|
5
21
|
beforeInsert?(event: InsertEvent): void | Promise<void>;
|
|
22
|
+
beforeUpdate?(event: UpdateEvent): void | Promise<void>;
|
|
23
|
+
beforeQuery?(event: BeforeQueryEvent): void | Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/** The part of a TypeORM `DataSource` that the registration helper uses. */
|
|
26
|
+
export interface TypeOrmDataSourceLike {
|
|
27
|
+
readonly isInitialized: boolean;
|
|
28
|
+
readonly subscribers: unknown[];
|
|
6
29
|
}
|
|
7
30
|
/**
|
|
8
|
-
* TypeORM subscriber that injects tenant_id into inserted entities via ALS
|
|
31
|
+
* TypeORM subscriber that injects tenant_id into inserted entities via ALS and
|
|
32
|
+
* never lets an update change tenant_id.
|
|
33
|
+
*
|
|
34
|
+
* Register it with `registerStratumSubscriber(dataSource)` after
|
|
35
|
+
* `dataSource.initialize()`. TypeORM's `subscribers` option only loads classes
|
|
36
|
+
* decorated with `@EventSubscriber()`, so it does not load this class.
|
|
9
37
|
*
|
|
10
38
|
* Known limitation: TypeORM subscribers cannot intercept query filtering.
|
|
11
39
|
* This subscriber handles writes only. Use the shared-table adapter's
|
|
@@ -14,5 +42,33 @@ export interface EntitySubscriberInterface {
|
|
|
14
42
|
export declare class StratumTypeOrmSubscriber implements EntitySubscriberInterface {
|
|
15
43
|
/** Injects the current tenant's ID into the entity before insert. */
|
|
16
44
|
beforeInsert(event: InsertEvent): void;
|
|
45
|
+
/**
|
|
46
|
+
* Keeps tenant_id out of an update. On save() the loaded value is restored,
|
|
47
|
+
* so the column is unchanged; on a repository or query builder update the
|
|
48
|
+
* column is dropped from the SET values (MySQL column names are
|
|
49
|
+
* case-insensitive, so any letter case is dropped).
|
|
50
|
+
*/
|
|
51
|
+
beforeUpdate(event: UpdateEvent): void;
|
|
52
|
+
/**
|
|
53
|
+
* Rejects an upsert that writes tenant_id on a key conflict.
|
|
54
|
+
*
|
|
55
|
+
* A conflict update must never change the tenant of an existing row. A
|
|
56
|
+
* subscriber cannot remove one column from the conflict update, so the
|
|
57
|
+
* statement fails before it runs.
|
|
58
|
+
*
|
|
59
|
+
* @throws Error when the ON DUPLICATE KEY UPDATE clause assigns tenant_id.
|
|
60
|
+
*/
|
|
61
|
+
beforeQuery(event: BeforeQueryEvent): void;
|
|
17
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Returns the one StratumTypeOrmSubscriber on the data source, and adds it first when it is missing.
|
|
65
|
+
*
|
|
66
|
+
* A second call returns the subscriber that the first call added, so the
|
|
67
|
+
* data source never runs the subscriber twice.
|
|
68
|
+
*
|
|
69
|
+
* @param dataSource - An initialized TypeORM `DataSource`.
|
|
70
|
+
* @throws Error when the data source is not initialized. `initialize()` replaces
|
|
71
|
+
* the subscriber list, so an earlier registration has no effect.
|
|
72
|
+
*/
|
|
73
|
+
export declare function registerStratumSubscriber(dataSource: TypeOrmDataSourceLike): StratumTypeOrmSubscriber;
|
|
18
74
|
//# sourceMappingURL=typeorm-subscriber.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typeorm-subscriber.d.ts","sourceRoot":"","sources":["../../src/integrations/typeorm-subscriber.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"typeorm-subscriber.d.ts","sourceRoot":"","sources":["../../src/integrations/typeorm-subscriber.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE;YAAE,YAAY,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,CAAC;CAC1E;AAED,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,YAAY,CAAC,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,WAAW,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAED,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC;CACjC;AAOD;;;;;;;;;;;GAWG;AACH,qBAAa,wBAAyB,YAAW,yBAAyB;IACxE,qEAAqE;IACrE,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAMtC;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAuBtC;;;;;;;;OAQG;IACH,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;CAS3C;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,qBAAqB,GAChC,wBAAwB,CAgB1B"}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StratumTypeOrmSubscriber = void 0;
|
|
4
|
+
exports.registerStratumSubscriber = registerStratumSubscriber;
|
|
4
5
|
const sdk_1 = require("@stratum-hq/sdk");
|
|
5
6
|
const utils_js_1 = require("../utils.js");
|
|
7
|
+
// TypeORM writes an upsert on MySQL as INSERT ... ON DUPLICATE KEY UPDATE and
|
|
8
|
+
// quotes each column with backticks.
|
|
9
|
+
const UPSERT_CLAUSE = /\bON\s+DUPLICATE\s+KEY\s+UPDATE\b/i;
|
|
10
|
+
const TENANT_ASSIGNMENT = /`tenant_id`\s*=/i;
|
|
6
11
|
/**
|
|
7
|
-
* TypeORM subscriber that injects tenant_id into inserted entities via ALS
|
|
12
|
+
* TypeORM subscriber that injects tenant_id into inserted entities via ALS and
|
|
13
|
+
* never lets an update change tenant_id.
|
|
14
|
+
*
|
|
15
|
+
* Register it with `registerStratumSubscriber(dataSource)` after
|
|
16
|
+
* `dataSource.initialize()`. TypeORM's `subscribers` option only loads classes
|
|
17
|
+
* decorated with `@EventSubscriber()`, so it does not load this class.
|
|
8
18
|
*
|
|
9
19
|
* Known limitation: TypeORM subscribers cannot intercept query filtering.
|
|
10
20
|
* This subscriber handles writes only. Use the shared-table adapter's
|
|
@@ -17,6 +27,70 @@ class StratumTypeOrmSubscriber {
|
|
|
17
27
|
(0, utils_js_1.assertTenantId)(context.tenant_id);
|
|
18
28
|
event.entity["tenant_id"] = context.tenant_id;
|
|
19
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Keeps tenant_id out of an update. On save() the loaded value is restored,
|
|
32
|
+
* so the column is unchanged; on a repository or query builder update the
|
|
33
|
+
* column is dropped from the SET values (MySQL column names are
|
|
34
|
+
* case-insensitive, so any letter case is dropped).
|
|
35
|
+
*/
|
|
36
|
+
beforeUpdate(event) {
|
|
37
|
+
const entity = event.entity;
|
|
38
|
+
if (!entity)
|
|
39
|
+
return;
|
|
40
|
+
const tenantProps = new Set(Object.keys(entity).filter((key) => key.toLowerCase() === "tenant_id"));
|
|
41
|
+
for (const column of event.metadata?.columns ?? []) {
|
|
42
|
+
if (column.databaseName.toLowerCase() === "tenant_id" && column.propertyName in entity) {
|
|
43
|
+
tenantProps.add(column.propertyName);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const prop of tenantProps) {
|
|
47
|
+
const loaded = event.databaseEntity;
|
|
48
|
+
if (loaded && prop in loaded) {
|
|
49
|
+
entity[prop] = loaded[prop];
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
delete entity[prop];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Rejects an upsert that writes tenant_id on a key conflict.
|
|
58
|
+
*
|
|
59
|
+
* A conflict update must never change the tenant of an existing row. A
|
|
60
|
+
* subscriber cannot remove one column from the conflict update, so the
|
|
61
|
+
* statement fails before it runs.
|
|
62
|
+
*
|
|
63
|
+
* @throws Error when the ON DUPLICATE KEY UPDATE clause assigns tenant_id.
|
|
64
|
+
*/
|
|
65
|
+
beforeQuery(event) {
|
|
66
|
+
const clause = event.query.split(UPSERT_CLAUSE)[1];
|
|
67
|
+
if (clause !== undefined && TENANT_ASSIGNMENT.test(clause)) {
|
|
68
|
+
throw new Error("Stratum: an upsert must not update tenant_id on conflict. " +
|
|
69
|
+
"Remove tenant_id from the entity values or from the orUpdate() columns.");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
20
72
|
}
|
|
21
73
|
exports.StratumTypeOrmSubscriber = StratumTypeOrmSubscriber;
|
|
74
|
+
/**
|
|
75
|
+
* Returns the one StratumTypeOrmSubscriber on the data source, and adds it first when it is missing.
|
|
76
|
+
*
|
|
77
|
+
* A second call returns the subscriber that the first call added, so the
|
|
78
|
+
* data source never runs the subscriber twice.
|
|
79
|
+
*
|
|
80
|
+
* @param dataSource - An initialized TypeORM `DataSource`.
|
|
81
|
+
* @throws Error when the data source is not initialized. `initialize()` replaces
|
|
82
|
+
* the subscriber list, so an earlier registration has no effect.
|
|
83
|
+
*/
|
|
84
|
+
function registerStratumSubscriber(dataSource) {
|
|
85
|
+
if (!dataSource.isInitialized) {
|
|
86
|
+
throw new Error("Stratum: call registerStratumSubscriber() after dataSource.initialize(). " +
|
|
87
|
+
"initialize() replaces the subscriber list.");
|
|
88
|
+
}
|
|
89
|
+
const existing = dataSource.subscribers.find((subscriber) => subscriber instanceof StratumTypeOrmSubscriber);
|
|
90
|
+
if (existing)
|
|
91
|
+
return existing;
|
|
92
|
+
const subscriber = new StratumTypeOrmSubscriber();
|
|
93
|
+
dataSource.subscribers.push(subscriber);
|
|
94
|
+
return subscriber;
|
|
95
|
+
}
|
|
22
96
|
//# sourceMappingURL=typeorm-subscriber.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typeorm-subscriber.js","sourceRoot":"","sources":["../../src/integrations/typeorm-subscriber.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"typeorm-subscriber.js","sourceRoot":"","sources":["../../src/integrations/typeorm-subscriber.ts"],"names":[],"mappings":";;;AAsHA,8DAkBC;AAxID,yCAAmD;AACnD,0CAA6C;AAiC7C,8EAA8E;AAC9E,qCAAqC;AACrC,MAAM,aAAa,GAAG,oCAAoC,CAAC;AAC3D,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAE7C;;;;;;;;;;;GAWG;AACH,MAAa,wBAAwB;IACnC,qEAAqE;IACrE,YAAY,CAAC,KAAkB;QAC7B,MAAM,OAAO,GAAG,IAAA,sBAAgB,GAAE,CAAC;QACnC,IAAA,yBAAc,EAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAClC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;IAChD,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,KAAkB;QAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,MAAM;YAAE,OAAO;QAEpB,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,WAAW,CAAC,CACvE,CAAC;QACF,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,OAAO,IAAI,EAAE,EAAE,CAAC;YACnD,IAAI,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE,KAAK,WAAW,IAAI,MAAM,CAAC,YAAY,IAAI,MAAM,EAAE,CAAC;gBACvF,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,cAAc,CAAC;YACpC,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;gBAC7B,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YAC9B,CAAC;iBAAM,CAAC;gBACN,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,WAAW,CAAC,KAAuB;QACjC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,MAAM,KAAK,SAAS,IAAI,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CACb,4DAA4D;gBAC1D,yEAAyE,CAC5E,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAvDD,4DAuDC;AAED;;;;;;;;;GASG;AACH,SAAgB,yBAAyB,CACvC,UAAiC;IAEjC,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,2EAA2E;YACzE,4CAA4C,CAC/C,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,CAAC,IAAI,CAC1C,CAAC,UAAU,EAA0C,EAAE,CACrD,UAAU,YAAY,wBAAwB,CACjD,CAAC;IACF,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,UAAU,GAAG,IAAI,wBAAwB,EAAE,CAAC;IAClD,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxC,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
package/dist/pool-manager.d.ts
CHANGED
|
@@ -10,7 +10,8 @@ import type { MysqlPoolLike, MysqlPoolManagerOptions } from "./types.js";
|
|
|
10
10
|
*/
|
|
11
11
|
export declare class MysqlPoolManager {
|
|
12
12
|
private readonly pools;
|
|
13
|
-
|
|
13
|
+
/** Pools that closePool or closeAll removed while a caller held them, by slug. */
|
|
14
|
+
private readonly closedEntries;
|
|
14
15
|
private readonly createPool;
|
|
15
16
|
private readonly baseUri;
|
|
16
17
|
private readonly maxPools;
|
|
@@ -38,8 +39,17 @@ export declare class MysqlPoolManager {
|
|
|
38
39
|
getStats(): {
|
|
39
40
|
poolCount: number;
|
|
40
41
|
};
|
|
41
|
-
/**
|
|
42
|
-
private
|
|
42
|
+
/** Keeps a removed pool that callers still hold, so that their releases do not reach a later pool for the slug. */
|
|
43
|
+
private recordClosedHolds;
|
|
44
|
+
/** Stops tracking a removed pool after its last hold ends. */
|
|
45
|
+
private forgetClosedIfReleased;
|
|
46
|
+
/** Ends the evicted pool first, then creates the pool for the slug. */
|
|
47
|
+
private openPool;
|
|
48
|
+
/**
|
|
49
|
+
* Removes the least-recently-used pool that no caller holds from the map and
|
|
50
|
+
* returns its entry. The caller ends it. Returns undefined when every pool is held.
|
|
51
|
+
*/
|
|
52
|
+
private takeLRU;
|
|
43
53
|
/** Closes pools that have been idle longer than idleTimeoutMs and have no active references. */
|
|
44
54
|
private closeIdlePools;
|
|
45
55
|
/** Builds the MySQL connection URI with the given database name. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool-manager.d.ts","sourceRoot":"","sources":["../src/pool-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"pool-manager.d.ts","sourceRoot":"","sources":["../src/pool-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAUzE;;;;;;;;GAQG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0C;IACxE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA0D;IACrF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAC3D,0EAA0E;IAC1E,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAqB;gBAEpC,OAAO,EAAE,uBAAuB;IAgB5C;;;;OAIG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA2BnD;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAqB/B,0EAA0E;IACpE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5C,2FAA2F;IACrF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ/B,qDAAqD;IACrD,QAAQ,IAAI;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE;IAIjC,mHAAmH;IACnH,OAAO,CAAC,iBAAiB;IAUzB,8DAA8D;IAC9D,OAAO,CAAC,sBAAsB;IAO9B,uEAAuE;YACzD,QAAQ;IAOtB;;;OAGG;IACH,OAAO,CAAC,OAAO;IAiBf,gGAAgG;YAClF,cAAc;IAe5B,oEAAoE;IACpE,OAAO,CAAC,QAAQ;CAYjB"}
|
package/dist/pool-manager.js
CHANGED
|
@@ -13,7 +13,8 @@ const core_1 = require("@stratum-hq/core");
|
|
|
13
13
|
*/
|
|
14
14
|
class MysqlPoolManager {
|
|
15
15
|
pools = new Map();
|
|
16
|
-
|
|
16
|
+
/** Pools that closePool or closeAll removed while a caller held them, by slug. */
|
|
17
|
+
closedEntries = new Map();
|
|
17
18
|
createPool;
|
|
18
19
|
baseUri;
|
|
19
20
|
maxPools;
|
|
@@ -41,37 +42,28 @@ class MysqlPoolManager {
|
|
|
41
42
|
*/
|
|
42
43
|
async getPool(slug) {
|
|
43
44
|
(0, core_1.validateSlug)(slug);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
// The lookup, the eviction choice and the insert run with no await between
|
|
46
|
+
// them. Thus a concurrent call for the same slug always finds this entry and
|
|
47
|
+
// waits for the same pool.
|
|
48
|
+
let entry = this.pools.get(slug);
|
|
49
|
+
if (!entry) {
|
|
50
|
+
const victim = this.pools.size >= this.maxPools ? this.takeLRU() : undefined;
|
|
51
|
+
entry = { ready: this.openPool(slug, victim), lastUsed: Date.now(), refCount: 0 };
|
|
52
|
+
this.pools.set(slug, entry);
|
|
49
53
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (inflight) {
|
|
53
|
-
const pool = await inflight;
|
|
54
|
-
const entry = this.pools.get(slug);
|
|
55
|
-
if (entry) {
|
|
56
|
-
entry.refCount++;
|
|
57
|
-
}
|
|
58
|
-
return pool;
|
|
59
|
-
}
|
|
60
|
-
// Evict before adding so we never exceed maxPools.
|
|
61
|
-
if (this.pools.size >= this.maxPools) {
|
|
62
|
-
await this.evictLRU();
|
|
63
|
-
}
|
|
64
|
-
const dbName = `stratum_tenant_${slug}`;
|
|
65
|
-
const uri = this.buildUri(dbName);
|
|
66
|
-
const createPromise = Promise.resolve(this.createPool(uri));
|
|
67
|
-
this.pending.set(slug, createPromise);
|
|
54
|
+
entry.lastUsed = Date.now();
|
|
55
|
+
entry.refCount++;
|
|
68
56
|
try {
|
|
69
|
-
|
|
70
|
-
this.pools.set(slug, { pool, lastUsed: Date.now(), refCount: 1 });
|
|
71
|
-
return pool;
|
|
57
|
+
return await entry.ready;
|
|
72
58
|
}
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
catch (err) {
|
|
60
|
+
// Remove the failed entry so that the next call tries again. The caller
|
|
61
|
+
// gets no pool, so it will not call releasePool.
|
|
62
|
+
if (this.pools.get(slug) === entry)
|
|
63
|
+
this.pools.delete(slug);
|
|
64
|
+
entry.refCount--;
|
|
65
|
+
this.forgetClosedIfReleased(slug, entry);
|
|
66
|
+
throw err;
|
|
75
67
|
}
|
|
76
68
|
}
|
|
77
69
|
/**
|
|
@@ -79,12 +71,24 @@ class MysqlPoolManager {
|
|
|
79
71
|
* No-op if the slug is not tracked.
|
|
80
72
|
*/
|
|
81
73
|
releasePool(slug) {
|
|
74
|
+
// The caller passes a slug, not the pool, so the manager cannot tell a
|
|
75
|
+
// hold on a closed pool from a hold on its replacement. It ends holds on
|
|
76
|
+
// closed pools first. The replacement then counts as held for longer than
|
|
77
|
+
// it is, which is safe: eviction never ends a pool in use.
|
|
78
|
+
const closed = this.closedEntries.get(slug)?.values().next().value;
|
|
79
|
+
if (closed) {
|
|
80
|
+
closed.refCount--;
|
|
81
|
+
this.forgetClosedIfReleased(slug, closed);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
82
84
|
const entry = this.pools.get(slug);
|
|
83
85
|
if (!entry)
|
|
84
86
|
return;
|
|
85
87
|
if (entry.refCount > 0) {
|
|
86
88
|
entry.refCount--;
|
|
87
89
|
}
|
|
90
|
+
// The idle timeout counts from the end of the last use, not from its start.
|
|
91
|
+
entry.lastUsed = Date.now();
|
|
88
92
|
}
|
|
89
93
|
/** Closes and removes the pool for the given slug. No-op if not found. */
|
|
90
94
|
async closePool(slug) {
|
|
@@ -92,21 +96,55 @@ class MysqlPoolManager {
|
|
|
92
96
|
if (!entry)
|
|
93
97
|
return;
|
|
94
98
|
this.pools.delete(slug);
|
|
95
|
-
|
|
99
|
+
this.recordClosedHolds(slug, entry);
|
|
100
|
+
await endEntry(entry);
|
|
96
101
|
}
|
|
97
102
|
/** Closes all managed pools and stops the idle timer. Call during application shutdown. */
|
|
98
103
|
async closeAll() {
|
|
99
104
|
clearInterval(this.idleTimer);
|
|
100
105
|
const entries = Array.from(this.pools.entries());
|
|
101
106
|
this.pools.clear();
|
|
102
|
-
|
|
107
|
+
for (const [slug, entry] of entries)
|
|
108
|
+
this.recordClosedHolds(slug, entry);
|
|
109
|
+
await Promise.all(entries.map(([, entry]) => endEntry(entry)));
|
|
103
110
|
}
|
|
104
111
|
/** Returns a snapshot of current pool statistics. */
|
|
105
112
|
getStats() {
|
|
106
113
|
return { poolCount: this.pools.size };
|
|
107
114
|
}
|
|
108
|
-
/**
|
|
109
|
-
|
|
115
|
+
/** Keeps a removed pool that callers still hold, so that their releases do not reach a later pool for the slug. */
|
|
116
|
+
recordClosedHolds(slug, entry) {
|
|
117
|
+
if (entry.refCount === 0)
|
|
118
|
+
return;
|
|
119
|
+
let closed = this.closedEntries.get(slug);
|
|
120
|
+
if (!closed) {
|
|
121
|
+
closed = new Set();
|
|
122
|
+
this.closedEntries.set(slug, closed);
|
|
123
|
+
}
|
|
124
|
+
closed.add(entry);
|
|
125
|
+
}
|
|
126
|
+
/** Stops tracking a removed pool after its last hold ends. */
|
|
127
|
+
forgetClosedIfReleased(slug, entry) {
|
|
128
|
+
const closed = this.closedEntries.get(slug);
|
|
129
|
+
if (!closed || entry.refCount > 0)
|
|
130
|
+
return;
|
|
131
|
+
closed.delete(entry);
|
|
132
|
+
if (closed.size === 0)
|
|
133
|
+
this.closedEntries.delete(slug);
|
|
134
|
+
}
|
|
135
|
+
/** Ends the evicted pool first, then creates the pool for the slug. */
|
|
136
|
+
async openPool(slug, victim) {
|
|
137
|
+
// The evicted pool belongs to a different tenant. Its end error must not
|
|
138
|
+
// fail this request.
|
|
139
|
+
if (victim)
|
|
140
|
+
await endEntry(victim).catch(() => undefined);
|
|
141
|
+
return this.createPool(this.buildUri(`stratum_tenant_${slug}`));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Removes the least-recently-used pool that no caller holds from the map and
|
|
145
|
+
* returns its entry. The caller ends it. Returns undefined when every pool is held.
|
|
146
|
+
*/
|
|
147
|
+
takeLRU() {
|
|
110
148
|
let oldestKey;
|
|
111
149
|
let oldestTime = Infinity;
|
|
112
150
|
for (const [key, entry] of this.pools.entries()) {
|
|
@@ -115,9 +153,11 @@ class MysqlPoolManager {
|
|
|
115
153
|
oldestKey = key;
|
|
116
154
|
}
|
|
117
155
|
}
|
|
118
|
-
if (oldestKey
|
|
119
|
-
|
|
120
|
-
|
|
156
|
+
if (oldestKey === undefined)
|
|
157
|
+
return undefined;
|
|
158
|
+
const entry = this.pools.get(oldestKey);
|
|
159
|
+
this.pools.delete(oldestKey);
|
|
160
|
+
return entry;
|
|
121
161
|
}
|
|
122
162
|
/** Closes pools that have been idle longer than idleTimeoutMs and have no active references. */
|
|
123
163
|
async closeIdlePools() {
|
|
@@ -128,7 +168,9 @@ class MysqlPoolManager {
|
|
|
128
168
|
toClose.push(key);
|
|
129
169
|
}
|
|
130
170
|
}
|
|
131
|
-
|
|
171
|
+
// The timer awaits nothing, so a rejection here would be unhandled. One
|
|
172
|
+
// pool that fails to end must not stop the others from closing.
|
|
173
|
+
await Promise.allSettled(toClose.map((slug) => this.closePool(slug)));
|
|
132
174
|
}
|
|
133
175
|
/** Builds the MySQL connection URI with the given database name. */
|
|
134
176
|
buildUri(dbName) {
|
|
@@ -146,4 +188,9 @@ class MysqlPoolManager {
|
|
|
146
188
|
}
|
|
147
189
|
}
|
|
148
190
|
exports.MysqlPoolManager = MysqlPoolManager;
|
|
191
|
+
/** Ends the entry's pool. A pool that failed to be created has nothing to end. */
|
|
192
|
+
async function endEntry(entry) {
|
|
193
|
+
const pool = await entry.ready.catch(() => undefined);
|
|
194
|
+
await pool?.end();
|
|
195
|
+
}
|
|
149
196
|
//# sourceMappingURL=pool-manager.js.map
|
package/dist/pool-manager.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool-manager.js","sourceRoot":"","sources":["../src/pool-manager.ts"],"names":[],"mappings":";;;AAAA,2CAAgD;
|
|
1
|
+
{"version":3,"file":"pool-manager.js","sourceRoot":"","sources":["../src/pool-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -37,6 +37,14 @@ export interface MysqlTableAdapterOptions {
|
|
|
37
37
|
pool: MysqlPoolLike;
|
|
38
38
|
/** The database name to use. */
|
|
39
39
|
databaseName: string;
|
|
40
|
+
/**
|
|
41
|
+
* Every base table name that has a per-tenant copy. Required by
|
|
42
|
+
* purgeTenantData, which drops exactly `{base}_{slug}` for each entry and
|
|
43
|
+
* nothing else. When set, scopedTable only accepts these base names.
|
|
44
|
+
* No entry may be another entry followed by `_`, since `{base}_{slug}` would
|
|
45
|
+
* then be ambiguous between two tenants.
|
|
46
|
+
*/
|
|
47
|
+
baseTables?: string[];
|
|
40
48
|
}
|
|
41
49
|
export interface MysqlDatabaseAdapterOptions {
|
|
42
50
|
/** Factory function to create a new pool for a connection URI. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,OAAO,IAAI,IAAI,CAAC;IAChB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC9C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAID,MAAM,WAAW,YAAY;IAC3B,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1D,QAAQ,IAAI,YAAY,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAID,MAAM,WAAW,yBAAyB;IACxC,yCAAyC;IACzC,IAAI,EAAE,aAAa,CAAC;IACpB,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,yCAAyC;IACzC,IAAI,EAAE,aAAa,CAAC;IACpB,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,OAAO,IAAI,IAAI,CAAC;IAChB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC9C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAID,MAAM,WAAW,YAAY;IAC3B,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1D,QAAQ,IAAI,YAAY,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAID,MAAM,WAAW,yBAAyB;IACxC,yCAAyC;IACzC,IAAI,EAAE,aAAa,CAAC;IACpB,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,yCAAyC;IACzC,IAAI,EAAE,aAAa,CAAC;IACpB,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,kEAAkE;IAClE,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACpE,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,kEAAkE;IAClE,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACpE,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
|
package/dist/views/manager.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { MysqlPoolLike, MysqlConnectionLike } from "../types.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @deprecated Not supported on MySQL, and always throws.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Use the shared-table adapter for high-performance workloads.
|
|
5
|
+
* MySQL rejects a view whose SELECT reads a user variable
|
|
6
|
+
* (ER_VIEW_SELECT_VARIABLE), so a view filtered on `@stratum_tenant_id` cannot
|
|
7
|
+
* be created. Use the shared-table adapter's scoped methods instead.
|
|
9
8
|
*/
|
|
10
|
-
export declare function createTenantView(
|
|
9
|
+
export declare function createTenantView(_pool: MysqlPoolLike, _tableName: string, _viewName?: string): Promise<void>;
|
|
11
10
|
/** Drops a tenant view if it exists. */
|
|
12
11
|
export declare function dropTenantView(pool: MysqlPoolLike, viewName: string): Promise<void>;
|
|
13
12
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/views/manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGtE
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/views/manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGtE;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,aAAa,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wCAAwC;AACxC,wBAAsB,cAAc,CAClC,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,mBAAmB,EAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
package/dist/views/manager.js
CHANGED
|
@@ -5,20 +5,15 @@ exports.dropTenantView = dropTenantView;
|
|
|
5
5
|
exports.setTenantSession = setTenantSession;
|
|
6
6
|
const utils_js_1 = require("../utils.js");
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* @deprecated Not supported on MySQL, and always throws.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Use the shared-table adapter for high-performance workloads.
|
|
10
|
+
* MySQL rejects a view whose SELECT reads a user variable
|
|
11
|
+
* (ER_VIEW_SELECT_VARIABLE), so a view filtered on `@stratum_tenant_id` cannot
|
|
12
|
+
* be created. Use the shared-table adapter's scoped methods instead.
|
|
14
13
|
*/
|
|
15
|
-
async function createTenantView(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const escapedTable = (0, utils_js_1.escapeIdentifier)(tableName);
|
|
19
|
-
const sql = `CREATE OR REPLACE VIEW ${escapedView} AS ` +
|
|
20
|
-
`SELECT * FROM ${escapedTable} WHERE tenant_id = @stratum_tenant_id`;
|
|
21
|
-
await pool.query(sql);
|
|
14
|
+
async function createTenantView(_pool, _tableName, _viewName) {
|
|
15
|
+
throw new Error("createTenantView is not supported: MySQL does not allow a view to read the " +
|
|
16
|
+
"@stratum_tenant_id session variable. Use MysqlSharedAdapter's scoped methods instead.");
|
|
22
17
|
}
|
|
23
18
|
/** Drops a tenant view if it exists. */
|
|
24
19
|
async function dropTenantView(pool, viewName) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/views/manager.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/views/manager.ts"],"names":[],"mappings":";;AAUA,4CASC;AAGD,wCAMC;AAMD,4CASC;AA1CD,0CAA+C;AAE/C;;;;;;GAMG;AACI,KAAK,UAAU,gBAAgB,CACpC,KAAoB,EACpB,UAAkB,EAClB,SAAkB;IAElB,MAAM,IAAI,KAAK,CACb,6EAA6E;QAC3E,uFAAuF,CAC1F,CAAC;AACJ,CAAC;AAED,wCAAwC;AACjC,KAAK,UAAU,cAAc,CAClC,IAAmB,EACnB,QAAgB;IAEhB,MAAM,WAAW,GAAG,IAAA,2BAAgB,EAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,IAAI,CAAC,KAAK,CAAC,uBAAuB,WAAW,EAAE,CAAC,CAAC;AACzD,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,gBAAgB,CACpC,UAA+B,EAC/B,QAAuB;IAEvB,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,UAAU,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC;IAC5D,CAAC;SAAM,CAAC;QACN,MAAM,UAAU,CAAC,OAAO,CAAC,4BAA4B,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrE,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stratum-hq/mysql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MySQL tenant isolation for Stratum — shared table, table-per-tenant, and database-per-tenant strategies",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"multi-tenancy",
|
|
@@ -38,11 +38,14 @@
|
|
|
38
38
|
"clean": "rm -rf dist"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@stratum-hq/core": "
|
|
42
|
-
"@stratum-hq/sdk": "
|
|
41
|
+
"@stratum-hq/core": "^1.4.0",
|
|
42
|
+
"@stratum-hq/sdk": "^1.2.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"mysql2": "^3.0.0"
|
|
45
|
+
"mysql2": "^3.0.0",
|
|
46
|
+
"knex": ">=3.0.0",
|
|
47
|
+
"typeorm": "^1.0.0",
|
|
48
|
+
"sequelize": "^6.0.0"
|
|
46
49
|
},
|
|
47
50
|
"peerDependenciesMeta": {
|
|
48
51
|
"typeorm": {
|
|
@@ -56,9 +59,11 @@
|
|
|
56
59
|
}
|
|
57
60
|
},
|
|
58
61
|
"devDependencies": {
|
|
59
|
-
"
|
|
62
|
+
"knex": "^3.3.0",
|
|
63
|
+
"mysql2": "^3.24.4",
|
|
64
|
+
"typeorm": "^1.1.1",
|
|
60
65
|
"typescript": "^5.9.0",
|
|
61
|
-
"vitest": "^1.
|
|
66
|
+
"vitest": "^4.1.11"
|
|
62
67
|
},
|
|
63
68
|
"publishConfig": {
|
|
64
69
|
"access": "public"
|