@zudojs/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/README.md +34 -0
- package/dist/cache.d.ts +2 -0
- package/dist/cache.js +29 -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 +9 -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({
|
|
@@ -123,8 +127,14 @@ export class CacheService {
|
|
|
123
127
|
? { metadata: options.metadata }
|
|
124
128
|
: {}),
|
|
125
129
|
});
|
|
126
|
-
if (result.success
|
|
127
|
-
|
|
130
|
+
if (result.success) {
|
|
131
|
+
// The entry now carries exactly the tags of this write. Mappings
|
|
132
|
+
// left over from an earlier write would let `invalidateByTag` on a
|
|
133
|
+
// tag the entry no longer has delete the new value.
|
|
134
|
+
await this.tagStore.removeKey?.(fullKey);
|
|
135
|
+
if (options?.tags && options.tags.length > 0)
|
|
136
|
+
await this.tagStore.add(fullKey, options.tags, this.tagScope(options));
|
|
137
|
+
}
|
|
128
138
|
return result;
|
|
129
139
|
}
|
|
130
140
|
catch (error) {
|
|
@@ -139,7 +149,7 @@ export class CacheService {
|
|
|
139
149
|
const fullKey = this.keyBuilder.build(key, options);
|
|
140
150
|
try {
|
|
141
151
|
const result = await this.store.delete(fullKey);
|
|
142
|
-
this.tagStore.removeKey(fullKey);
|
|
152
|
+
await this.tagStore.removeKey?.(fullKey);
|
|
143
153
|
return result;
|
|
144
154
|
}
|
|
145
155
|
catch (error) {
|
|
@@ -178,7 +188,7 @@ export class CacheService {
|
|
|
178
188
|
const pattern = this.qualifyPattern(options.pattern ?? "*", options.namespace);
|
|
179
189
|
try {
|
|
180
190
|
const result = await this.store.clear({ pattern });
|
|
181
|
-
this.purgeTagsMatching(pattern);
|
|
191
|
+
await this.purgeTagsMatching(pattern);
|
|
182
192
|
return result;
|
|
183
193
|
}
|
|
184
194
|
catch (error) {
|
|
@@ -189,7 +199,7 @@ export class CacheService {
|
|
|
189
199
|
}
|
|
190
200
|
try {
|
|
191
201
|
const result = await this.store.clear();
|
|
192
|
-
this.tagStore.clear();
|
|
202
|
+
await this.tagStore.clear?.();
|
|
193
203
|
return result;
|
|
194
204
|
}
|
|
195
205
|
catch (error) {
|
|
@@ -427,7 +437,10 @@ export class CacheService {
|
|
|
427
437
|
// Drop service-local state so a reconnect does not resurrect stale
|
|
428
438
|
// in-flight computations or tag mappings.
|
|
429
439
|
this.inFlight.clear();
|
|
430
|
-
|
|
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?.();
|
|
431
444
|
}
|
|
432
445
|
/* ---- Internals ---- */
|
|
433
446
|
async applyBatchOperation(operation, options) {
|
|
@@ -453,6 +466,7 @@ export class CacheService {
|
|
|
453
466
|
/** The namespace scope applied to tag registrations and lookups. */
|
|
454
467
|
tagScope(options) {
|
|
455
468
|
const namespace = options?.namespace ?? this.namespace;
|
|
469
|
+
assertNonEmptyNamespace(namespace);
|
|
456
470
|
return namespace !== undefined ? { namespace } : {};
|
|
457
471
|
}
|
|
458
472
|
qualifyPattern(pattern, namespace) {
|
|
@@ -461,11 +475,14 @@ export class CacheService {
|
|
|
461
475
|
}
|
|
462
476
|
return pattern;
|
|
463
477
|
}
|
|
464
|
-
purgeTagsMatching(pattern) {
|
|
478
|
+
async purgeTagsMatching(pattern) {
|
|
479
|
+
const store = this.tagStore;
|
|
480
|
+
if (!store.trackedKeys || !store.removeKey)
|
|
481
|
+
return;
|
|
465
482
|
const matches = createGlobMatcher(pattern, { separator: this.separator });
|
|
466
|
-
for (const key of
|
|
483
|
+
for (const key of await store.trackedKeys()) {
|
|
467
484
|
if (matches(key))
|
|
468
|
-
|
|
485
|
+
await store.removeKey(key);
|
|
469
486
|
}
|
|
470
487
|
}
|
|
471
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,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/cache",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Caching primitives, abstractions, and adapters for the Zudojs framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Oluwayemi Oyinlola",
|
|
8
|
+
"url": "https://github.com/oyinlola-tech"
|
|
9
|
+
},
|
|
6
10
|
"type": "module",
|
|
7
11
|
"main": "./dist/index.js",
|
|
8
12
|
"module": "./dist/index.js",
|
|
@@ -20,9 +24,10 @@
|
|
|
20
24
|
"!dist/.tsbuildinfo"
|
|
21
25
|
],
|
|
22
26
|
"dependencies": {
|
|
23
|
-
"@zudojs/
|
|
24
|
-
"@zudojs/
|
|
25
|
-
"@zudojs/serialization": "1.
|
|
27
|
+
"@zudojs/constants": "1.1.0",
|
|
28
|
+
"@zudojs/errors": "1.1.0",
|
|
29
|
+
"@zudojs/serialization": "1.1.0",
|
|
30
|
+
"@zudojs/types": "1.1.0"
|
|
26
31
|
},
|
|
27
32
|
"devDependencies": {
|
|
28
33
|
"typescript": "7.0.2",
|