@basaltkit/cache 1.2.1 → 1.3.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
@@ -211,6 +211,7 @@ const cacheB = new Cache(new RedisCacheDriver(redis))
211
211
  |---|---|---|---|---|
212
212
  | `prefix` | `string` | No | `'basalt'` | Root prefix for all keys. |
213
213
  | `scope` | `(() => string \| undefined) \| null` | No | reads `ctx().tenant.id` → `tenant:<id>` | Dynamic prefix segment, resolved on each operation. `null` disables tenant isolation. |
214
+ | `onMissingScope` | `'global' \| 'error'` | No | `'error'` when `tenancyPlugin` is registered, else `'global'` | What an operation does when the scope resolves to `undefined` (e.g. a background job outside request context). In multi-tenant apps the default fails CLOSED (`MissingCacheScopeError`) instead of silently sharing one namespace across tenants; single-tenant apps keep the global namespace. Explicit values always win. |
214
215
 
215
216
  ### `cachePlugin(options?: CachePluginOptions)`
216
217
 
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { BasaltError, createToken, definePlugin, parseDuration, tryCtx, } from '@basaltkit/core';
1
+ import { BasaltError, createToken, definePlugin, ensureMetadata, parseDuration, tryCtx, } from '@basaltkit/core';
2
2
  import { MemoryCacheDriver } from './drivers/memory.js';
3
3
  import { RedisCacheDriver } from './drivers/redis.js';
4
4
  export { MemoryCacheDriver } from './drivers/memory.js';
@@ -169,7 +169,16 @@ export function cachePlugin(options = {}) {
169
169
  : options.driver === 'redis'
170
170
  ? RedisCacheDriver.fromUrl(options.url)
171
171
  : new MemoryCacheDriver();
172
- return new Cache(driver, options);
172
+ // Fail closed by default in multi-tenant apps: when @basaltkit/tenancy
173
+ // is registered (its 'tenancy:active' metadata marker), an operation
174
+ // with no resolvable tenant scope throws instead of silently sharing
175
+ // one global namespace across tenants. Single-tenant apps (no tenancy)
176
+ // are untouched, and an explicit `onMissingScope`/custom `scope` wins.
177
+ const tenancyActive = ensureMetadata(container).get('tenancy:active').length > 0;
178
+ const resolved = options.onMissingScope === undefined && options.scope === undefined && tenancyActive
179
+ ? { ...options, onMissingScope: 'error' }
180
+ : options;
181
+ return new Cache(driver, resolved);
173
182
  });
174
183
  },
175
184
  async shutdown() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basaltkit/cache",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Basalt cache layer: Redis and Memory drivers, tags, TTL, stampede protection and automatic per-tenant isolation.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "dependencies": {
17
17
  "ioredis": "^6.0.0",
18
- "@basaltkit/core": "^1.1.2"
18
+ "@basaltkit/core": "^1.3.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^26.3.0",