@zudojs/cache 1.0.1 → 1.1.1
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 +34 -0
- package/dist/cache.d.ts +2 -0
- package/dist/cache.js +23 -12
- package/dist/invalidation.js +2 -2
- package/dist/key-builder.d.ts +11 -0
- package/dist/key-builder.js +18 -2
- package/dist/lock.js +31 -2
- package/dist/serializer.d.ts +11 -7
- package/dist/serializer.js +18 -13
- package/dist/types-config.d.ts +9 -0
- package/dist/types-tags.d.ts +15 -4
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Cache abstraction with a memory adapter, namespaced tags, locking, events, middleware, and metrics for Zudojs applications.
|
|
4
4
|
|
|
5
|
+
<!-- zudo-docs:start -->
|
|
6
|
+
|
|
7
|
+
**Documentation:** [zudojs.oyinlola.site/docs/packages-cache](https://zudojs.oyinlola.site/docs/packages-cache) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-cache.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
|
|
8
|
+
|
|
9
|
+
<!-- zudo-docs:end -->
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -74,6 +80,34 @@ await cache.withLock("import", runImport, { namespace: tenantId });
|
|
|
74
80
|
In glob patterns, `*` matches within a single key segment and never crosses
|
|
75
81
|
the `:` separator; `**` as a whole segment spans namespaces deliberately.
|
|
76
82
|
|
|
83
|
+
An empty-string namespace is rejected with `CACHE_INVALID_KEY`, in the config
|
|
84
|
+
and per call. A tenant id that failed to resolve to `""` can therefore never
|
|
85
|
+
fall through to the unscoped global keyspace. Omit `namespace` to use that
|
|
86
|
+
keyspace deliberately.
|
|
87
|
+
|
|
88
|
+
## Tags across instances
|
|
89
|
+
|
|
90
|
+
By default each `CacheService` keeps its tag mappings in process, so
|
|
91
|
+
`invalidateByTag` only sees entries written by the same instance. Replicas that
|
|
92
|
+
share one adapter must also share a tag store. Pass any `CacheTagStore`
|
|
93
|
+
implementation (for example one backed by Redis sets) as `config.tagStore`:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const tagStore = createTagStore(); // or your shared implementation
|
|
97
|
+
const a = createCacheService({ adapter, config: { tagStore } });
|
|
98
|
+
const b = createCacheService({ adapter, config: { tagStore } });
|
|
99
|
+
|
|
100
|
+
await a.set("user.1", user, { tags: ["users"] });
|
|
101
|
+
await b.invalidateByTag(["users"]); // clears the entry a wrote
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Serialization
|
|
105
|
+
|
|
106
|
+
`JsonCacheSerializer` preserves `Date`, `BigInt`, `Map`, `Set` and `Uint8Array`
|
|
107
|
+
by default and drops `__proto__`, `constructor` and `prototype` keys on read.
|
|
108
|
+
A value with its own `$type` field (a domain discriminator) is ordinary data
|
|
109
|
+
and reads back unchanged.
|
|
110
|
+
|
|
77
111
|
## Observability
|
|
78
112
|
|
|
79
113
|
```typescript
|
package/dist/cache.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export declare class CacheService implements CacheHealthChecker {
|
|
|
25
25
|
private readonly store;
|
|
26
26
|
private readonly keyBuilder;
|
|
27
27
|
private readonly tagStore;
|
|
28
|
+
/** False when the tag store was injected and may be shared with peers. */
|
|
29
|
+
private readonly ownsTagStore;
|
|
28
30
|
private readonly invalidation;
|
|
29
31
|
private readonly lockManager;
|
|
30
32
|
private readonly metrics;
|
package/dist/cache.js
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
* patterns), so it can never widen an operation beyond its own tenant.
|
|
16
16
|
*/
|
|
17
17
|
import { DEFAULT_LOCK_RETRY_DELAY_MS, DEFAULT_SEPARATOR, DEFAULT_TTL_MS, } from "./constants.js";
|
|
18
|
-
import { DefaultKeyBuilder } from "./key-builder.js";
|
|
18
|
+
import { assertNonEmptyNamespace, DefaultKeyBuilder } from "./key-builder.js";
|
|
19
19
|
import { createCacheStore, DefaultCacheStore } from "./store.js";
|
|
20
|
-
import { assertValidTag, createTagStore
|
|
20
|
+
import { assertValidTag, createTagStore } from "./tags.js";
|
|
21
21
|
import { CacheInvalidationManager, createInvalidationManager, } from "./invalidation.js";
|
|
22
22
|
import { CacheLockManager, createLockManager } from "./lock.js";
|
|
23
23
|
import { createCacheMetrics, InMemoryCacheMetrics } from "./metrics.js";
|
|
@@ -27,6 +27,8 @@ export class CacheService {
|
|
|
27
27
|
store;
|
|
28
28
|
keyBuilder;
|
|
29
29
|
tagStore;
|
|
30
|
+
/** False when the tag store was injected and may be shared with peers. */
|
|
31
|
+
ownsTagStore;
|
|
30
32
|
invalidation;
|
|
31
33
|
lockManager;
|
|
32
34
|
metrics;
|
|
@@ -48,6 +50,7 @@ export class CacheService {
|
|
|
48
50
|
: DEFAULT_TTL_MS;
|
|
49
51
|
this.serializer = options.config?.serializer ?? null;
|
|
50
52
|
this.namespace = options.config?.namespace;
|
|
53
|
+
assertNonEmptyNamespace(this.namespace);
|
|
51
54
|
this.separator = options.config?.separator ?? DEFAULT_SEPARATOR;
|
|
52
55
|
this.metrics =
|
|
53
56
|
options.config?.collectStats !== false ? createCacheMetrics() : null;
|
|
@@ -71,7 +74,8 @@ export class CacheService {
|
|
|
71
74
|
? { namespace: options.config.namespace }
|
|
72
75
|
: {}),
|
|
73
76
|
});
|
|
74
|
-
this.tagStore = createTagStore();
|
|
77
|
+
this.tagStore = options.config?.tagStore ?? createTagStore();
|
|
78
|
+
this.ownsTagStore = options.config?.tagStore === undefined;
|
|
75
79
|
// Route invalidation through the instrumented store so metrics and
|
|
76
80
|
// events fire for invalidation-driven deletions too.
|
|
77
81
|
this.invalidation = createInvalidationManager({
|
|
@@ -127,7 +131,7 @@ export class CacheService {
|
|
|
127
131
|
// The entry now carries exactly the tags of this write. Mappings
|
|
128
132
|
// left over from an earlier write would let `invalidateByTag` on a
|
|
129
133
|
// tag the entry no longer has delete the new value.
|
|
130
|
-
this.tagStore.removeKey(fullKey);
|
|
134
|
+
await this.tagStore.removeKey?.(fullKey);
|
|
131
135
|
if (options?.tags && options.tags.length > 0)
|
|
132
136
|
await this.tagStore.add(fullKey, options.tags, this.tagScope(options));
|
|
133
137
|
}
|
|
@@ -145,7 +149,7 @@ export class CacheService {
|
|
|
145
149
|
const fullKey = this.keyBuilder.build(key, options);
|
|
146
150
|
try {
|
|
147
151
|
const result = await this.store.delete(fullKey);
|
|
148
|
-
this.tagStore.removeKey(fullKey);
|
|
152
|
+
await this.tagStore.removeKey?.(fullKey);
|
|
149
153
|
return result;
|
|
150
154
|
}
|
|
151
155
|
catch (error) {
|
|
@@ -184,7 +188,7 @@ export class CacheService {
|
|
|
184
188
|
const pattern = this.qualifyPattern(options.pattern ?? "*", options.namespace);
|
|
185
189
|
try {
|
|
186
190
|
const result = await this.store.clear({ pattern });
|
|
187
|
-
this.purgeTagsMatching(pattern);
|
|
191
|
+
await this.purgeTagsMatching(pattern);
|
|
188
192
|
return result;
|
|
189
193
|
}
|
|
190
194
|
catch (error) {
|
|
@@ -195,7 +199,7 @@ export class CacheService {
|
|
|
195
199
|
}
|
|
196
200
|
try {
|
|
197
201
|
const result = await this.store.clear();
|
|
198
|
-
this.tagStore.clear();
|
|
202
|
+
await this.tagStore.clear?.();
|
|
199
203
|
return result;
|
|
200
204
|
}
|
|
201
205
|
catch (error) {
|
|
@@ -294,7 +298,7 @@ export class CacheService {
|
|
|
294
298
|
const qualified = this.qualifyPattern(pattern, options?.namespace);
|
|
295
299
|
try {
|
|
296
300
|
const result = await this.invalidation.invalidateByPattern(qualified);
|
|
297
|
-
this.purgeTagsMatching(qualified);
|
|
301
|
+
await this.purgeTagsMatching(qualified);
|
|
298
302
|
return result;
|
|
299
303
|
}
|
|
300
304
|
catch (error) {
|
|
@@ -433,7 +437,10 @@ export class CacheService {
|
|
|
433
437
|
// Drop service-local state so a reconnect does not resurrect stale
|
|
434
438
|
// in-flight computations or tag mappings.
|
|
435
439
|
this.inFlight.clear();
|
|
436
|
-
|
|
440
|
+
// An injected store may be shared with other instances: their mappings
|
|
441
|
+
// are not this instance's to drop.
|
|
442
|
+
if (this.ownsTagStore)
|
|
443
|
+
await this.tagStore.clear?.();
|
|
437
444
|
}
|
|
438
445
|
/* ---- Internals ---- */
|
|
439
446
|
async applyBatchOperation(operation, options) {
|
|
@@ -459,6 +466,7 @@ export class CacheService {
|
|
|
459
466
|
/** The namespace scope applied to tag registrations and lookups. */
|
|
460
467
|
tagScope(options) {
|
|
461
468
|
const namespace = options?.namespace ?? this.namespace;
|
|
469
|
+
assertNonEmptyNamespace(namespace);
|
|
462
470
|
return namespace !== undefined ? { namespace } : {};
|
|
463
471
|
}
|
|
464
472
|
qualifyPattern(pattern, namespace) {
|
|
@@ -467,11 +475,14 @@ export class CacheService {
|
|
|
467
475
|
}
|
|
468
476
|
return pattern;
|
|
469
477
|
}
|
|
470
|
-
purgeTagsMatching(pattern) {
|
|
478
|
+
async purgeTagsMatching(pattern) {
|
|
479
|
+
const store = this.tagStore;
|
|
480
|
+
if (!store.trackedKeys || !store.removeKey)
|
|
481
|
+
return;
|
|
471
482
|
const matches = createGlobMatcher(pattern, { separator: this.separator });
|
|
472
|
-
for (const key of
|
|
483
|
+
for (const key of await store.trackedKeys()) {
|
|
473
484
|
if (matches(key))
|
|
474
|
-
|
|
485
|
+
await store.removeKey(key);
|
|
475
486
|
}
|
|
476
487
|
}
|
|
477
488
|
serialize(value) {
|
package/dist/invalidation.js
CHANGED
|
@@ -50,7 +50,7 @@ export class CacheInvalidationManager {
|
|
|
50
50
|
cleared++;
|
|
51
51
|
// The key is gone from the cache, so every tag mapping it still has
|
|
52
52
|
// (in any scope) is dead too.
|
|
53
|
-
this.tagStore.removeKey?.(key);
|
|
53
|
+
await this.tagStore.removeKey?.(key);
|
|
54
54
|
}
|
|
55
55
|
for (const tag of tags) {
|
|
56
56
|
await this.tagStore.invalidate(tag, options);
|
|
@@ -97,7 +97,7 @@ export class CacheInvalidationManager {
|
|
|
97
97
|
*/
|
|
98
98
|
async flushAll() {
|
|
99
99
|
const result = await this.adapter.clear();
|
|
100
|
-
this.tagStore.clear?.();
|
|
100
|
+
await this.tagStore.clear?.();
|
|
101
101
|
return result;
|
|
102
102
|
}
|
|
103
103
|
}
|
package/dist/key-builder.d.ts
CHANGED
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { CacheKey, CacheKeyOptions, CacheNamespace } from "./types.js";
|
|
12
12
|
import type { CacheKeyBuilder } from "./types-keys.js";
|
|
13
|
+
/**
|
|
14
|
+
* Rejects an empty-string namespace.
|
|
15
|
+
*
|
|
16
|
+
* `""` is falsy, so it used to be dropped as "no namespace" and moved a
|
|
17
|
+
* tenant whose id failed to resolve into the shared global keyspace. A
|
|
18
|
+
* namespace is either absent (`undefined`) or a valid identity part.
|
|
19
|
+
*
|
|
20
|
+
* @param namespace - The namespace to check.
|
|
21
|
+
* @throws {CacheError} `CACHE_INVALID_KEY` when `namespace` is `""`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function assertNonEmptyNamespace(namespace: CacheNamespace | undefined): void;
|
|
13
24
|
/**
|
|
14
25
|
* Default implementation of the `CacheKeyBuilder` contract.
|
|
15
26
|
*
|
package/dist/key-builder.js
CHANGED
|
@@ -11,6 +11,21 @@
|
|
|
11
11
|
import { CACHE_KEY_PATTERN, DEFAULT_PREFIX, DEFAULT_SEPARATOR, MAX_KEY_LENGTH, } from "./constants.js";
|
|
12
12
|
import { cacheInvalidKeyError } from "./errors.js";
|
|
13
13
|
import { assertValidPatternPart } from "./utils.js";
|
|
14
|
+
/**
|
|
15
|
+
* Rejects an empty-string namespace.
|
|
16
|
+
*
|
|
17
|
+
* `""` is falsy, so it used to be dropped as "no namespace" and moved a
|
|
18
|
+
* tenant whose id failed to resolve into the shared global keyspace. A
|
|
19
|
+
* namespace is either absent (`undefined`) or a valid identity part.
|
|
20
|
+
*
|
|
21
|
+
* @param namespace - The namespace to check.
|
|
22
|
+
* @throws {CacheError} `CACHE_INVALID_KEY` when `namespace` is `""`.
|
|
23
|
+
*/
|
|
24
|
+
export function assertNonEmptyNamespace(namespace) {
|
|
25
|
+
if (namespace === "") {
|
|
26
|
+
throw cacheInvalidKeyError(namespace, "Cache namespace must not be empty. Omit it for the unscoped keyspace.");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
14
29
|
/* -------------------------------------------------------------------------- */
|
|
15
30
|
/* Default Key Builder */
|
|
16
31
|
/* -------------------------------------------------------------------------- */
|
|
@@ -26,6 +41,7 @@ export class DefaultKeyBuilder {
|
|
|
26
41
|
constructor(options) {
|
|
27
42
|
this.globalPrefix = options?.prefix ?? DEFAULT_PREFIX;
|
|
28
43
|
this.globalSeparator = options?.separator ?? DEFAULT_SEPARATOR;
|
|
44
|
+
assertNonEmptyNamespace(options?.namespace);
|
|
29
45
|
this.currentNamespace = options?.namespace;
|
|
30
46
|
}
|
|
31
47
|
build(key, options) {
|
|
@@ -39,7 +55,7 @@ export class DefaultKeyBuilder {
|
|
|
39
55
|
if (prefix) {
|
|
40
56
|
parts.push(prefix);
|
|
41
57
|
}
|
|
42
|
-
if (namespace) {
|
|
58
|
+
if (namespace !== undefined) {
|
|
43
59
|
parts.push(namespace);
|
|
44
60
|
}
|
|
45
61
|
parts.push(key);
|
|
@@ -68,7 +84,7 @@ export class DefaultKeyBuilder {
|
|
|
68
84
|
const identityParts = [];
|
|
69
85
|
if (prefix)
|
|
70
86
|
identityParts.push(prefix);
|
|
71
|
-
if (namespace)
|
|
87
|
+
if (namespace !== undefined)
|
|
72
88
|
identityParts.push(namespace);
|
|
73
89
|
for (const part of identityParts)
|
|
74
90
|
this.validatePart(part, separator);
|
package/dist/lock.js
CHANGED
|
@@ -203,8 +203,18 @@ export class CacheLockManager {
|
|
|
203
203
|
// `release()` returning false is the one signal that the lease expired or
|
|
204
204
|
// was taken over mid-critical-section. Surfacing it is the whole point of
|
|
205
205
|
// minting a fencing token. An error thrown by `fn` still wins, so the
|
|
206
|
-
// real cause is never masked
|
|
207
|
-
|
|
206
|
+
// real cause is never masked — including by `release()` itself throwing
|
|
207
|
+
// (a distributed store that is down). The release error is then attached
|
|
208
|
+
// to the thrown error as a non-enumerable `suppressed` property.
|
|
209
|
+
let released = false;
|
|
210
|
+
try {
|
|
211
|
+
released = await lock.release();
|
|
212
|
+
}
|
|
213
|
+
catch (releaseError) {
|
|
214
|
+
if (failed)
|
|
215
|
+
throw attachSuppressed(failure, releaseError);
|
|
216
|
+
throw releaseError;
|
|
217
|
+
}
|
|
208
218
|
if (failed)
|
|
209
219
|
throw failure;
|
|
210
220
|
if (!released || leaseLost) {
|
|
@@ -219,6 +229,25 @@ export class CacheLockManager {
|
|
|
219
229
|
return value;
|
|
220
230
|
}
|
|
221
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Record a secondary error on the primary one without replacing it, mirroring
|
|
234
|
+
* `SuppressedError.suppressed`. Only an extensible object that does not
|
|
235
|
+
* already carry `suppressed` is annotated; the primary is returned either way.
|
|
236
|
+
*/
|
|
237
|
+
function attachSuppressed(primary, secondary) {
|
|
238
|
+
if (typeof primary === "object" &&
|
|
239
|
+
primary !== null &&
|
|
240
|
+
Object.isExtensible(primary) &&
|
|
241
|
+
!Object.hasOwn(primary, "suppressed")) {
|
|
242
|
+
Object.defineProperty(primary, "suppressed", {
|
|
243
|
+
value: secondary,
|
|
244
|
+
enumerable: false,
|
|
245
|
+
configurable: true,
|
|
246
|
+
writable: true,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return primary;
|
|
250
|
+
}
|
|
222
251
|
function sleep(ms) {
|
|
223
252
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
224
253
|
}
|
package/dist/serializer.d.ts
CHANGED
|
@@ -9,13 +9,17 @@ import type { CacheSerializer } from "./types.js";
|
|
|
9
9
|
/**
|
|
10
10
|
* Strips prototype-polluting own keys from a freshly deserialized value.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* A cached payload can come from a shared backing store, a restored backup,
|
|
13
|
+
* or another writer, so the bytes are not necessarily ones this process
|
|
14
|
+
* serialized. `JSON.parse` leaves `__proto__` as a genuine own property,
|
|
15
|
+
* which re-triggers the setter on any later spread or `Object.assign`.
|
|
16
|
+
*
|
|
17
|
+
* The key list is `SCHEMA_FORBIDDEN_KEYS` from `@zudojs/constants`.
|
|
18
|
+
*
|
|
19
|
+
* `JsonCacheSerializer` needs this only for `preserveTypes: false`: on the
|
|
20
|
+
* type-preserving path `@zudojs/serialization` already drops these keys
|
|
21
|
+
* (`allowUnsafeKeys: false`), but its plain-JSON fast path returns
|
|
22
|
+
* `JSON.parse` output untouched.
|
|
19
23
|
*/
|
|
20
24
|
export declare function stripUnsafeKeys<T>(value: T): T;
|
|
21
25
|
/**
|
package/dist/serializer.js
CHANGED
|
@@ -5,21 +5,25 @@
|
|
|
5
5
|
* to and from storable representations. Delegates to @zudojs/serialization
|
|
6
6
|
* for the actual JSON serialization with type preservation.
|
|
7
7
|
*/
|
|
8
|
+
import { SCHEMA_FORBIDDEN_KEYS } from "@zudojs/constants";
|
|
8
9
|
import { JSONSerializer } from "@zudojs/serialization";
|
|
9
10
|
/* -------------------------------------------------------------------------- */
|
|
10
11
|
/* Prototype-pollution hardening */
|
|
11
12
|
/* -------------------------------------------------------------------------- */
|
|
12
|
-
const UNSAFE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
13
13
|
/**
|
|
14
14
|
* Strips prototype-polluting own keys from a freshly deserialized value.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
16
|
+
* A cached payload can come from a shared backing store, a restored backup,
|
|
17
|
+
* or another writer, so the bytes are not necessarily ones this process
|
|
18
|
+
* serialized. `JSON.parse` leaves `__proto__` as a genuine own property,
|
|
19
|
+
* which re-triggers the setter on any later spread or `Object.assign`.
|
|
20
|
+
*
|
|
21
|
+
* The key list is `SCHEMA_FORBIDDEN_KEYS` from `@zudojs/constants`.
|
|
22
|
+
*
|
|
23
|
+
* `JsonCacheSerializer` needs this only for `preserveTypes: false`: on the
|
|
24
|
+
* type-preserving path `@zudojs/serialization` already drops these keys
|
|
25
|
+
* (`allowUnsafeKeys: false`), but its plain-JSON fast path returns
|
|
26
|
+
* `JSON.parse` output untouched.
|
|
23
27
|
*/
|
|
24
28
|
export function stripUnsafeKeys(value) {
|
|
25
29
|
const seen = new Set();
|
|
@@ -46,7 +50,7 @@ export function stripUnsafeKeys(value) {
|
|
|
46
50
|
return;
|
|
47
51
|
}
|
|
48
52
|
for (const key of Object.getOwnPropertyNames(obj)) {
|
|
49
|
-
if (
|
|
53
|
+
if (SCHEMA_FORBIDDEN_KEYS.has(key)) {
|
|
50
54
|
Reflect.deleteProperty(obj, key);
|
|
51
55
|
continue;
|
|
52
56
|
}
|
|
@@ -106,14 +110,15 @@ export class JsonCacheSerializer {
|
|
|
106
110
|
});
|
|
107
111
|
}
|
|
108
112
|
deserialize(value) {
|
|
113
|
+
// Not `strict`: strict mode throws on any `$type` the registry does not
|
|
114
|
+
// know, so a domain object carrying a `$type` discriminator could be
|
|
115
|
+
// written but never read back, poisoning its key. An unknown tag is
|
|
116
|
+
// ordinary data.
|
|
109
117
|
const parsed = this.inner.deserialize(value, {
|
|
110
118
|
preserveTypes: this.preserveTypes,
|
|
111
|
-
// Stated at the call site even though the published sibling build
|
|
112
|
-
// ignores them; `stripUnsafeKeys` enforces the intent regardless.
|
|
113
|
-
strict: true,
|
|
114
119
|
allowUnsafeKeys: false,
|
|
115
120
|
});
|
|
116
|
-
return stripUnsafeKeys(parsed);
|
|
121
|
+
return this.preserveTypes ? parsed : stripUnsafeKeys(parsed);
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
/* -------------------------------------------------------------------------- */
|
package/dist/types-config.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { CacheTTL } from "./types-values.js";
|
|
|
3
3
|
import type { CacheMiddleware } from "./types-metrics.js";
|
|
4
4
|
import type { CacheSerializer } from "./types-health.js";
|
|
5
5
|
import type { CacheLockStore } from "./types-lock.js";
|
|
6
|
+
import type { CacheTagStore } from "./types-tags.js";
|
|
6
7
|
export interface CacheConfig {
|
|
7
8
|
readonly enabled?: boolean;
|
|
8
9
|
readonly defaultTtl?: CacheTTL;
|
|
@@ -45,6 +46,14 @@ export interface CacheConfig {
|
|
|
45
46
|
* processes.
|
|
46
47
|
*/
|
|
47
48
|
readonly lockStore?: CacheLockStore;
|
|
49
|
+
/**
|
|
50
|
+
* Tag store backing `set({ tags })` and `invalidateByTag`. Defaults to a
|
|
51
|
+
* fresh in-process store per service, which means tag invalidation only
|
|
52
|
+
* sees entries written by the same instance. Replicas sharing one adapter
|
|
53
|
+
* (one Redis) must share a tag store too, e.g. one backed by Redis sets.
|
|
54
|
+
* An injected store is never flushed by `disconnect()`.
|
|
55
|
+
*/
|
|
56
|
+
readonly tagStore?: CacheTagStore;
|
|
48
57
|
}
|
|
49
58
|
/**
|
|
50
59
|
* Error codes set by errors this package constructs directly. Every member
|
package/dist/types-tags.d.ts
CHANGED
|
@@ -14,9 +14,20 @@ export interface CacheTagStore {
|
|
|
14
14
|
remove(key: CacheKey, tags: readonly CacheTag[], options?: CacheTagOptions): Promise<void>;
|
|
15
15
|
getKeys(tag: CacheTag, options?: CacheTagOptions): Promise<readonly CacheKey[]>;
|
|
16
16
|
invalidate(tag: CacheTag, options?: CacheTagOptions): Promise<CacheClearResult>;
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Removes all tag mappings for a key, across every namespace.
|
|
19
|
+
*
|
|
20
|
+
* May be async: `CacheService` awaits it, so a shared (e.g. Redis-backed)
|
|
21
|
+
* store can complete the removal before the write is reported.
|
|
22
|
+
*/
|
|
23
|
+
removeKey?(key: CacheKey): void | Promise<void>;
|
|
24
|
+
/** Removes all tag mappings. May be async. */
|
|
25
|
+
clear?(): void | Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Every key that currently has at least one tag mapping. Used to drop the
|
|
28
|
+
* mappings of keys removed by a pattern clear; when absent, those mappings
|
|
29
|
+
* are left for the next write or tag invalidation of the key.
|
|
30
|
+
*/
|
|
31
|
+
trackedKeys?(): readonly CacheKey[] | Promise<readonly CacheKey[]>;
|
|
21
32
|
}
|
|
22
33
|
//# sourceMappingURL=types-tags.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/cache",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Caching primitives, abstractions, and adapters for the Zudojs framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
"!dist/.tsbuildinfo"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zudojs/
|
|
28
|
-
"@zudojs/
|
|
29
|
-
"@zudojs/serialization": "1.
|
|
27
|
+
"@zudojs/constants": "1.1.1",
|
|
28
|
+
"@zudojs/errors": "1.2.0",
|
|
29
|
+
"@zudojs/serialization": "1.1.1",
|
|
30
|
+
"@zudojs/types": "1.1.1"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"typescript": "7.0.2",
|