@stratum-hq/mysql 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,7 +46,7 @@ import { MysqlTableAdapter } from "@stratum-hq/mysql";
46
46
  const adapter = new MysqlTableAdapter({
47
47
  pool,
48
48
  databaseName: "myapp",
49
- // Every base table that has a per-tenant copy. Required by purgeTenantData.
49
+ // Every base table that has a per-tenant copy. Required by scopedTable and purgeTenantData.
50
50
  baseTables: ["users", "orders"],
51
51
  });
52
52
 
@@ -79,7 +79,7 @@ await adapter.closeAll();
79
79
 
80
80
  ## ORM Integrations
81
81
 
82
- ### TypeORM Subscriber (writes only)
82
+ ### TypeORM Subscriber
83
83
 
84
84
  ```typescript
85
85
  import { registerStratumSubscriber } from "@stratum-hq/mysql";
@@ -91,11 +91,21 @@ registerStratumSubscriber(dataSource);
91
91
 
92
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
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.
94
+ Inserts get the current tenant's `tenant_id`, in whichever entity property maps to the `tenant_id` column (a relation whose join column is `tenant_id` cannot set or change it). Updates never change `tenant_id`: `save()` keeps the loaded value, and `update()` / query builder updates drop it from the SET values. The insert and update query builders enforce both, so `save(entity, { listeners: false })` and `.callListeners(false)` do not skip them.
95
+
96
+ `registerStratumSubscriber()` also scopes updates and deletes to the current tenant: repository `update()`, `delete()`, `softDelete()`, `restore()`, `save()` of an existing row, `remove()`, and query builder updates and deletes get `tenant_id = <current tenant>` ANDed to their WHERE clause. A row of another tenant is left unchanged, and an update or delete of a tenant table outside a tenant context is refused. `save()` of a row that belongs to another tenant throws: before an insert (other than an upsert) whose entity supplies its whole primary key, the subscriber looks that key up without the read scope, uses the result only for this check, and refuses the insert when the row belongs to another tenant. An update or delete builder aimed at a raw table name (not an entity) is treated as a tenant table and always gets the tenant condition, so it fails on a table without `tenant_id`. `TRUNCATE` of a table with a `tenant_id` column (`repository.clear()`, `queryRunner.clearTable()`) is refused, because it would remove every tenant's rows. A subscriber added to `dataSource.subscribers` by hand refuses every UPDATE and DELETE, so always register it with `registerStratumSubscriber()`.
95
97
 
96
98
  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
99
 
98
- **Limitation:** TypeORM subscribers can intercept writes but not reads. Use the shared-table adapter's structured methods for tenant-scoped reads.
100
+ `INSERT ... SELECT` (`valuesFromSelect()`) into a tenant entity is refused, because the tenant of the selected rows cannot be set. Into another table it is allowed, and a SELECT of a tenant entity keeps its tenant condition.
101
+
102
+ MySQL applies `ON DUPLICATE KEY UPDATE` on a conflict with any unique key of the table, whatever conflict columns you pass. The subscriber therefore allows an upsert only when every unique key of the target table, including the primary key, contains `tenant_id` (for example `PRIMARY KEY (tenant_id, id)`). It reads the keys from `information_schema` before the statement runs, and rejects the upsert otherwise.
103
+
104
+ `registerStratumSubscriber()` also scopes reads. Every query that TypeORM's select query builder builds on the data source gets `tenant_id = <current tenant>`: repository `find*()`, `findOne*()`, `count*()`, `exists*()`, `sum()` / `average()` / `minimum()` / `maximum()` and `preload()`, query builder `getMany()`, `getOne()`, `getRawMany()`, `getRawOne()`, `getCount()`, `getManyAndCount()`, `getExists()` and `stream()`, relation loading (joins, eager relations, `relationLoadStrategy: "query"`), the row that `save()` loads before it writes, subqueries, and the count and pagination queries TypeORM builds internally. The condition is ANDed to the WHERE clause for the entity in FROM, so an `orWhere()` cannot widen the read, and it is added to the ON condition of every joined entity with a `tenant_id` column, so `leftJoinAndSelect()` still returns the parent row and leaves another tenant's related row out. A read of an entity with a `tenant_id` column outside a tenant context is refused. Entities without a `tenant_id` column, and data sources without the subscriber, are not affected.
105
+
106
+ A view entity whose `expression` is a query builder is created by `synchronize()` without the tenant condition, because the view definition is one schema object shared by all tenants. Reads from a view entity are scoped like reads from a table when the view has a `tenant_id` column. A view without a `tenant_id` column is read unscoped.
107
+
108
+ **Limitation:** raw SQL (`dataSource.query()`, `queryRunner.query()`), SQL taken from `getQuery()` / `getQueryAndParameters()` and run by hand (a select builder's SQL includes the tenant condition, but an update or delete builder adds it only when its `execute()` runs), reads from and inserts into a table name that does not exactly match (including letter case) the name or table name of an entity on the data source (such inserts are not given a tenant_id), and many-to-many junction tables are not scoped. Add the tenant condition to those yourself, or use the shared-table adapter's structured methods.
99
109
 
100
110
  ### Knex Helper
101
111
 
@@ -107,7 +117,9 @@ const users = await tenantKnex("users").where("name", "like", q).orWhere("email"
107
117
  // Compiles to: WHERE tenant_id = 'tenant-a' AND (name LIKE ? OR email LIKE ?)
108
118
  ```
109
119
 
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.
120
+ 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()`, `truncate()` and `modify()` throw (a `modify()` callback would call the builder without these rules).
121
+
122
+ Joins (`join()`, `leftJoin()`, `crossJoin()`, `joinRaw()` and the other join forms) and `union()` / `unionAll()` also throw, because the tenant filter covers only the builder's own table. To combine tables, use a tenant-scoped builder as a `whereIn()` subquery, or write the query with plain Knex and a `tenant_id` condition on every table.
111
123
 
112
124
  ### Sequelize Adapter
113
125
 
@@ -115,13 +127,29 @@ Your where clauses are always grouped after the tenant filter, including on clon
115
127
  import { withMysqlTenantScope } from "@stratum-hq/mysql";
116
128
 
117
129
  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.
120
- // Guaranteed cleanup via try/finally, even on errors
121
- const [rows] = await scoped.query("SELECT @stratum_tenant_id", { transaction });
130
+ // Models with a tenant_id attribute see and change only this tenant's rows.
131
+ const notes = await Note.findAll({ include: ["owner"], transaction });
132
+ await Note.create({ name: "new" }, { transaction }); // tenant_id is set for you
122
133
  });
123
134
  ```
124
135
 
136
+ Inside the callback, for every model with a `tenant_id` attribute:
137
+
138
+ - `findAll()`, `findOne()`, `findByPk()`, `findAndCountAll()`, `count()`, `sum()`, `min()`, `max()` and `reload()` return only the tenant's rows. Every include of a tenant model is filtered in its join condition, including includes added by default and named scopes, by an included model's own default scope, by `include: { all: true }` and by hooks, so an include that is not `required` still returns the parent row. A scope's own where clause is kept.
139
+ - `update()`, `destroy()`, `restore()`, `increment()` and `decrement()` change only the tenant's rows. Updates never write `tenant_id`, whether it is given by attribute name or column name, in any letter case, and `increment()` / `decrement()` of `tenant_id` are refused. As outside the helper, `update()`, `destroy()` and `increment()` without a `where` are refused by Sequelize.
140
+ - `create()`, `save()` of a new instance and `bulkCreate()` write the tenant's `tenant_id`, whatever value the caller passed, and add it to a `fields` list that leaves it out.
141
+ - `save()`, `destroy()` and `restore()` of an instance whose row belongs to another tenant throw, and so do they for an existing instance of a model without a primary key, whose row cannot be checked.
142
+ - `upsert()`, `bulkCreate()` with `updateOnDuplicate`, `truncate()`, and `or: true` or `right: true` on an include of a tenant model are refused.
143
+ - Right before each query, the tenant condition is checked again on the where clause and on every include of a tenant model. A query whose condition a hook removed (for example a `beforeFind` hook that replaces `options.where`, or a `beforeFindAfterOptions` hook that adds includes) is refused.
144
+ - A query whose options name a tenant model or instance but that did not go through these methods throws, also when it runs inside a model hook. This covers, for example, `queryInterface.select(Note, ...)`. `queryInterface` calls that take only a table name, such as `bulkInsert()`, `bulkUpdate()` and `bulkDelete()`, carry no model and are not scoped, like raw SQL.
145
+ - `hooks: false` does not skip the filter.
146
+
147
+ `upsert()` on a model with a `tenant_id` attribute is always refused inside the callback, because MySQL applies `ON DUPLICATE KEY UPDATE` on any unique key, so a conflict could update another tenant's row. Look the row up with `findOne()` and then `update()` or `create()` it instead.
148
+
149
+ The helper also sets `@stratum_tenant_id` on the transaction's connection and clears it in a `finally` block. Pass the transaction to queries that must run in it.
150
+
151
+ **Not scoped:** raw `sequelize.query()`, `queryInterface` calls by table name, models without a `tenant_id` attribute, the through (junction) model of a many-to-many include, and any code outside the callback. The helper throws when it is given something other than a Sequelize v6 instance.
152
+
125
153
  ## MySQL Views (not supported)
126
154
 
127
155
  `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.
@@ -13,6 +13,9 @@ export declare class MysqlTableAdapter implements MysqlAdapter {
13
13
  /**
14
14
  * Validates slug and returns the escaped tenant-scoped table name
15
15
  * in the form `{baseTableName}_{tenantSlug}`.
16
+ *
17
+ * Throws when baseTables is not set: without the list, two tenants can get
18
+ * the same name, e.g. ("corp_acme", "orders") and ("acme", "orders_corp").
16
19
  */
17
20
  scopedTable(tenantSlug: string, baseTableName: string): string;
18
21
  /** Returns the underlying pool for raw queries against tenant tables. */
@@ -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;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"}
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;;;;;;OAMG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM;IAe9D,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"}
@@ -31,10 +31,17 @@ class MysqlTableAdapter {
31
31
  /**
32
32
  * Validates slug and returns the escaped tenant-scoped table name
33
33
  * in the form `{baseTableName}_{tenantSlug}`.
34
+ *
35
+ * Throws when baseTables is not set: without the list, two tenants can get
36
+ * the same name, e.g. ("corp_acme", "orders") and ("acme", "orders_corp").
34
37
  */
35
38
  scopedTable(tenantSlug, baseTableName) {
36
39
  (0, core_1.validateSlug)(tenantSlug);
37
- if (this.baseTables && !this.baseTables.includes(baseTableName)) {
40
+ if (!this.baseTables) {
41
+ throw new Error("MysqlTableAdapter: scopedTable requires the baseTables option, " +
42
+ "listing every base table name that has a per-tenant copy");
43
+ }
44
+ if (!this.baseTables.includes(baseTableName)) {
38
45
  throw new Error(`MysqlTableAdapter: "${baseTableName}" is not in baseTables`);
39
46
  }
40
47
  const tableName = `${baseTableName}_${tenantSlug}`;
@@ -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;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"}
1
+ {"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/adapters/table.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,15 @@ export interface KnexLike {
26
26
  * UPDATE never changes tenant_id: the column is dropped from the update data.
27
27
  * onConflict().merge(), upsert() and truncate() throw, because MySQL applies
28
28
  * none of them through the WHERE clause. onConflict().ignore() is allowed.
29
+ *
30
+ * modify() throws, because its callback would call the builder's methods
31
+ * without the insert, update and onConflict rules above.
32
+ *
33
+ * Joins (join, innerJoin, leftJoin, crossJoin, joinRaw and the other forms)
34
+ * and union() / unionAll() throw, because the tenant filter covers only this
35
+ * builder's table, not the joined or unioned rows. To combine tenant data, use
36
+ * a tenant-scoped builder as a whereIn subquery, or write the query with an
37
+ * explicit tenant_id condition on every table.
29
38
  */
30
39
  export declare function withTenantScope(knex: KnexLike, tenantId: string): (tableName: string) => KnexQueryBuilderLike;
31
40
  //# 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;AA8BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,MAAM,GACf,CAAC,SAAS,EAAE,MAAM,KAAK,oBAAoB,CAwI7C"}
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;AAsDD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,MAAM,GACf,CAAC,SAAS,EAAE,MAAM,KAAK,oBAAoB,CAwI7C"}
@@ -3,8 +3,32 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.withTenantScope = withTenantScope;
5
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"]);
6
+ /**
7
+ * Builder methods the tenant filter cannot cover, mapped to the reason given
8
+ * in the error: writes that MySQL does not apply through the WHERE clause, and
9
+ * joins and unions, whose other rows the filter does not reach.
10
+ */
11
+ const NOT_FILTERED = "because MySQL does not apply the tenant filter to it";
12
+ const NOT_SCOPED = "because the tenant filter does not apply to the rows it adds";
13
+ const REFUSED_METHODS = new Map([
14
+ ["upsert", NOT_FILTERED],
15
+ ["truncate", NOT_FILTERED],
16
+ ["modify", "because the tenant rules for insert, update and onConflict do not apply inside its callback"],
17
+ ...[
18
+ "join",
19
+ "innerJoin",
20
+ "leftJoin",
21
+ "leftOuterJoin",
22
+ "rightJoin",
23
+ "rightOuterJoin",
24
+ "outerJoin",
25
+ "fullOuterJoin",
26
+ "crossJoin",
27
+ "joinRaw",
28
+ "union",
29
+ "unionAll",
30
+ ].map((method) => [method, NOT_SCOPED]),
31
+ ]);
8
32
  function isTenantColumn(name) {
9
33
  return name.toLowerCase() === TENANT_COLUMN;
10
34
  }
@@ -33,6 +57,15 @@ function withoutTenantColumn(row) {
33
57
  * UPDATE never changes tenant_id: the column is dropped from the update data.
34
58
  * onConflict().merge(), upsert() and truncate() throw, because MySQL applies
35
59
  * none of them through the WHERE clause. onConflict().ignore() is allowed.
60
+ *
61
+ * modify() throws, because its callback would call the builder's methods
62
+ * without the insert, update and onConflict rules above.
63
+ *
64
+ * Joins (join, innerJoin, leftJoin, crossJoin, joinRaw and the other forms)
65
+ * and union() / unionAll() throw, because the tenant filter covers only this
66
+ * builder's table, not the joined or unioned rows. To combine tenant data, use
67
+ * a tenant-scoped builder as a whereIn subquery, or write the query with an
68
+ * explicit tenant_id condition on every table.
36
69
  */
37
70
  function withTenantScope(knex, tenantId) {
38
71
  // Statement objects this module created, so they can be recognized again on
@@ -96,10 +129,10 @@ function withTenantScope(knex, tenantId) {
96
129
  if (typeof value !== "function" || typeof prop !== "string" || prop === "constructor") {
97
130
  return value;
98
131
  }
99
- if (REFUSED_METHODS.has(prop)) {
132
+ const refusal = REFUSED_METHODS.get(prop);
133
+ if (refusal) {
100
134
  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`);
135
+ throw new Error(`withTenantScope: ${prop}() is not allowed on a tenant-scoped builder, ${refusal}`);
103
136
  };
104
137
  }
105
138
  if (depth > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"knex.js","sourceRoot":"","sources":["../../src/integrations/knex.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"knex.js","sourceRoot":"","sources":["../../src/integrations/knex.ts"],"names":[],"mappings":"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"}
@@ -3,14 +3,36 @@ export interface SequelizeLike {
3
3
  transaction<T>(fn: (t: unknown) => Promise<T>): Promise<T>;
4
4
  }
5
5
  /**
6
- * Runs fn inside a session-variable scope for MySQL tenant isolation.
7
- * Sets @stratum_tenant_id inside a Sequelize transaction and clears it in a
8
- * finally block, even if fn throws.
6
+ * Runs fn with the tenant's rows scoped for Sequelize models, inside a
7
+ * transaction that also sets the @stratum_tenant_id session variable.
9
8
  *
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.
9
+ * Inside fn, for every model with a tenant_id attribute:
10
+ * - findAll, findOne, findByPk, findAndCountAll, count, sum, min, max and
11
+ * reload() see only the tenant's rows, and every include of a tenant model
12
+ * (including those that scopes and hooks add) is filtered in its join
13
+ * condition, so a non-required include keeps the parent;
14
+ * - update, destroy, restore and increment/decrement change only the tenant's
15
+ * rows; updates never write tenant_id (by attribute or column name), and
16
+ * increment/decrement of tenant_id is refused. update, destroy and increment
17
+ * without a where clause are still refused by Sequelize;
18
+ * - create, save of a new instance and bulkCreate write the tenant's tenant_id;
19
+ * - save, destroy and restore of an instance whose row belongs to another
20
+ * tenant, or of a model without a primary key, throw;
21
+ * - upsert, bulkCreate with updateOnDuplicate, truncate, and `or` / `right`
22
+ * on an include of a tenant model are refused, and a query that carries a
23
+ * tenant model but bypasses these methods throws, also inside model hooks;
24
+ * - right before each query the tenant condition is checked again, so a query
25
+ * whose condition a hook removed is refused.
26
+ * Hooks passed `hooks: false` do not skip the filter. Raw `sequelize.query()`
27
+ * and models without a tenant_id attribute are not scoped. Outside fn,
28
+ * Sequelize behaves as usual.
29
+ *
30
+ * The session variable exists only on the transaction's connection. fn
31
+ * receives that transaction as its second argument; pass it to queries that
32
+ * must run in the transaction.
33
+ *
34
+ * @throws Error when sequelize is not a Sequelize v6 instance, because the
35
+ * tenant scope could not be applied.
14
36
  */
15
37
  export declare function withMysqlTenantScope<T>(sequelize: SequelizeLike, tenantId: string, fn: (sequelize: SequelizeLike, transaction: unknown) => Promise<T>): Promise<T>;
16
38
  //# 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;;;;;;;;;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"}
1
+ {"version":3,"file":"sequelize.d.ts","sourceRoot":"","sources":["../../src/integrations/sequelize.ts"],"names":[],"mappings":"AAKA,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;AA+CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;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,CAcZ"}