@basaltkit/cache 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.js +16 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _basaltkit_core from '@basaltkit/core';
|
|
2
|
-
import { DurationInput } from '@basaltkit/core';
|
|
2
|
+
import { DurationInput, BasaltError } from '@basaltkit/core';
|
|
3
3
|
import { Redis } from 'ioredis';
|
|
4
4
|
|
|
5
5
|
/** Cache driver contract. Every driver passes the same conformance suite. */
|
|
@@ -37,19 +37,32 @@ declare class RedisCacheDriver implements CacheDriver {
|
|
|
37
37
|
disconnect(): Promise<void>;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
declare class MissingCacheScopeError extends BasaltError {
|
|
41
|
+
constructor(op: string);
|
|
42
|
+
}
|
|
40
43
|
interface CacheOptions {
|
|
41
44
|
/** Root prefix for all keys. Default: 'basalt' */
|
|
42
45
|
prefix?: string;
|
|
43
46
|
/**
|
|
44
47
|
* Dynamic segment of the prefix, resolved on every operation. The default reads
|
|
45
|
-
* `ctx().tenant.id` — automatic per-tenant isolation. Pass `null` to disable
|
|
48
|
+
* `ctx().tenant.id` — automatic per-tenant isolation. Pass `null` to disable
|
|
49
|
+
* (a deliberate global cache).
|
|
46
50
|
*/
|
|
47
51
|
scope?: (() => string | undefined) | null;
|
|
52
|
+
/**
|
|
53
|
+
* What to do when the scope function resolves nothing (no tenant in context):
|
|
54
|
+
* `'global'` (default) shares one namespace — convenient but a per-tenant value
|
|
55
|
+
* cached without a tenant leaks to others; `'error'` fails closed (throws
|
|
56
|
+
* {@link MissingCacheScopeError}) on read/write. `flush()` ALWAYS fails closed
|
|
57
|
+
* regardless, so a mis-scoped call can't wipe every tenant's cache.
|
|
58
|
+
*/
|
|
59
|
+
onMissingScope?: 'global' | 'error';
|
|
48
60
|
}
|
|
49
61
|
declare class Cache {
|
|
50
62
|
private readonly driver;
|
|
51
63
|
private readonly prefix;
|
|
52
64
|
private readonly scope;
|
|
65
|
+
private readonly onMissingScope;
|
|
53
66
|
/** dedupe of in-flight factories — per-process stampede protection */
|
|
54
67
|
private readonly pending;
|
|
55
68
|
constructor(driver: CacheDriver, options?: CacheOptions);
|
|
@@ -83,4 +96,4 @@ interface CachePluginOptions extends CacheOptions {
|
|
|
83
96
|
}
|
|
84
97
|
declare function cachePlugin(options?: CachePluginOptions): _basaltkit_core.BasaltPlugin<unknown>;
|
|
85
98
|
|
|
86
|
-
export { CACHE, Cache, type CacheDriver, type CacheOptions, type CachePluginOptions, MemoryCacheDriver, RedisCacheDriver, cachePlugin };
|
|
99
|
+
export { CACHE, Cache, type CacheDriver, type CacheOptions, type CachePluginOptions, MemoryCacheDriver, MissingCacheScopeError, RedisCacheDriver, cachePlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
+
BasaltError,
|
|
3
4
|
createToken,
|
|
4
5
|
definePlugin,
|
|
5
6
|
parseDuration,
|
|
@@ -94,6 +95,14 @@ var RedisCacheDriver = class _RedisCacheDriver {
|
|
|
94
95
|
};
|
|
95
96
|
|
|
96
97
|
// src/index.ts
|
|
98
|
+
var MissingCacheScopeError = class extends BasaltError {
|
|
99
|
+
constructor(op) {
|
|
100
|
+
super(
|
|
101
|
+
"CACHE_SCOPE_MISSING",
|
|
102
|
+
`Refusing cache ${op}: a tenant-scoped cache resolved no tenant (ran without a tenant context). Establish a tenant, or use scope:null for a deliberate global cache.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
97
106
|
var defaultScope = () => {
|
|
98
107
|
const tenant = tryCtx()?.["tenant"];
|
|
99
108
|
return tenant?.id ? `tenant:${tenant.id}` : void 0;
|
|
@@ -103,10 +112,12 @@ var Cache = class {
|
|
|
103
112
|
this.driver = driver;
|
|
104
113
|
this.prefix = options.prefix ?? "basalt";
|
|
105
114
|
this.scope = options.scope === void 0 ? defaultScope : options.scope;
|
|
115
|
+
this.onMissingScope = options.onMissingScope ?? "global";
|
|
106
116
|
}
|
|
107
117
|
driver;
|
|
108
118
|
prefix;
|
|
109
119
|
scope;
|
|
120
|
+
onMissingScope;
|
|
110
121
|
/** dedupe of in-flight factories — per-process stampede protection */
|
|
111
122
|
pending = /* @__PURE__ */ new Map();
|
|
112
123
|
async get(key, fallback) {
|
|
@@ -132,6 +143,7 @@ var Cache = class {
|
|
|
132
143
|
}
|
|
133
144
|
/** Clears only the keys under this prefix/scope — never the entire Redis. */
|
|
134
145
|
async flush() {
|
|
146
|
+
if (this.scope !== null && this.scope() === void 0) throw new MissingCacheScopeError("flush");
|
|
135
147
|
await this.driver.flushPrefix(this.root());
|
|
136
148
|
}
|
|
137
149
|
/** Tag-scoped operations: `cache.tags('plans').flush()` invalidates the group. */
|
|
@@ -171,7 +183,9 @@ var Cache = class {
|
|
|
171
183
|
return computation;
|
|
172
184
|
}
|
|
173
185
|
root() {
|
|
174
|
-
|
|
186
|
+
if (this.scope === null) return `${this.prefix}:`;
|
|
187
|
+
const scope = this.scope();
|
|
188
|
+
if (scope === void 0 && this.onMissingScope === "error") throw new MissingCacheScopeError("operation");
|
|
175
189
|
return scope ? `${this.prefix}:${scope}:` : `${this.prefix}:`;
|
|
176
190
|
}
|
|
177
191
|
key(key) {
|
|
@@ -198,6 +212,7 @@ export {
|
|
|
198
212
|
CACHE,
|
|
199
213
|
Cache,
|
|
200
214
|
MemoryCacheDriver,
|
|
215
|
+
MissingCacheScopeError,
|
|
201
216
|
RedisCacheDriver,
|
|
202
217
|
cachePlugin
|
|
203
218
|
};
|
package/package.json
CHANGED