@zudojs/cache 1.2.3 → 1.2.4
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 +21 -4
- package/dist/cache.d.ts +9 -3
- package/dist/cache.js +31 -25
- package/dist/constants.d.ts +12 -7
- package/dist/constants.js +14 -9
- package/dist/errors.d.ts +0 -1
- package/dist/errors.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/invalidation.d.ts +0 -1
- package/dist/invalidation.js +0 -1
- package/dist/key-builder.d.ts +0 -1
- package/dist/key-builder.js +9 -2
- package/dist/lock.d.ts +0 -1
- package/dist/lock.js +0 -1
- package/dist/memory.d.ts +0 -1
- package/dist/memory.js +0 -1
- package/dist/metrics.d.ts +0 -1
- package/dist/metrics.js +0 -1
- package/dist/serializer.d.ts +0 -1
- package/dist/serializer.js +0 -1
- package/dist/store.d.ts +0 -1
- package/dist/store.js +0 -1
- package/dist/tags.d.ts +0 -1
- package/dist/tags.js +0 -1
- package/dist/types-adapter.d.ts +8 -1
- package/dist/types-adapter.js +0 -1
- package/dist/types-config.d.ts +0 -1
- package/dist/types-config.js +0 -1
- package/dist/types-events.d.ts +0 -1
- package/dist/types-events.js +0 -1
- package/dist/types-health.d.ts +0 -1
- package/dist/types-health.js +0 -1
- package/dist/types-keys.d.ts +0 -1
- package/dist/types-keys.js +0 -1
- package/dist/types-lock.d.ts +0 -1
- package/dist/types-lock.js +0 -1
- package/dist/types-metrics.d.ts +0 -1
- package/dist/types-metrics.js +0 -1
- package/dist/types-operations.d.ts +0 -1
- package/dist/types-operations.js +0 -1
- package/dist/types-results.d.ts +0 -1
- package/dist/types-results.js +0 -1
- package/dist/types-tags.d.ts +0 -1
- package/dist/types-tags.js +0 -1
- package/dist/types-utility.d.ts +0 -1
- package/dist/types-utility.js +0 -1
- package/dist/types-values.d.ts +0 -1
- package/dist/types-values.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/dist/utils.d.ts +9 -1
- package/dist/utils.js +34 -3
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -45,10 +45,27 @@ const { value, cached } = await cache.getOrSet<User>(`user.${id}`, () =>
|
|
|
45
45
|
);
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
Key parts are validated individually
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
builder produces
|
|
48
|
+
Key parts (prefix, namespace, key) are validated individually: letters,
|
|
49
|
+
digits, `.`, `_`, `-` and `:`, minus the active separator. With the default
|
|
50
|
+
`:` separator that means no `:` inside a key — `prefix:namespace:key` is the
|
|
51
|
+
structure the key builder produces, so a key `a:b` would collide with key `b`
|
|
52
|
+
in namespace `a`, and `clear({ namespace: "a" })` would delete it. Put the
|
|
53
|
+
scope in `namespace` (`cache.get("dashboard", { namespace: tenantId })`), use
|
|
54
|
+
`.` or `-` inside a key (`user.123`, not `user:123`), or, if your keys must
|
|
55
|
+
contain `:` (keys built by another library, say), configure a different
|
|
56
|
+
separator on both the service and the memory adapter:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const cache = createCacheService({
|
|
60
|
+
adapter: createMemoryCacheAdapter({ separator: "/" }),
|
|
61
|
+
config: { separator: "/" },
|
|
62
|
+
});
|
|
63
|
+
await cache.set("tenant:kola:dashboard", totals); // zudojs/tenant:kola:dashboard
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
A rejected key throws a `CacheError` (`ERR_INVALID_INPUT`, status 400) whose
|
|
67
|
+
`operation` names the call that rejected it (`get`, `set`, `lock_acquire`,
|
|
68
|
+
`clear`, …) and whose message explains the separator rule.
|
|
52
69
|
|
|
53
70
|
## Features
|
|
54
71
|
|
package/dist/cache.d.ts
CHANGED
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* validated as an identity part everywhere it is used (including in glob
|
|
15
15
|
* patterns), so it can never widen an operation beyond its own tenant.
|
|
16
16
|
*/
|
|
17
|
-
import type { CacheAdapter, CacheBatchOperation, CacheBatchResult, CacheConfig, CacheDeleteResult, CacheEvent, CacheEventHandler, CacheEventSubscription, CacheGetResult, CacheHealth, CacheHealthChecker, CacheNamespace,
|
|
17
|
+
import type { CacheAdapter, CacheBatchOperation, CacheBatchResult, CacheConfig, CacheDeleteResult, CacheEvent, CacheEventHandler, CacheEventSubscription, CacheGetResult, CacheHealth, CacheHealthChecker, CacheNamespace, CacheOrComputeOptions, CacheOrComputeResult, CacheSetResult, CacheStats, CacheTag, CacheTTL } from "./types.js";
|
|
18
18
|
import type { CacheKeyBuilder } from "./types-keys.js";
|
|
19
19
|
import { InMemoryCacheMetrics } from "./metrics.js";
|
|
20
|
+
import { CacheOperation } from "./errors.js";
|
|
20
21
|
/** Options accepted by every namespace-scoped read operation. */
|
|
21
22
|
interface NamespaceOptions {
|
|
22
23
|
readonly namespace?: CacheNamespace;
|
|
@@ -44,6 +45,10 @@ export declare class CacheService implements CacheHealthChecker {
|
|
|
44
45
|
readonly config?: CacheConfig;
|
|
45
46
|
readonly keyBuilder?: CacheKeyBuilder;
|
|
46
47
|
});
|
|
48
|
+
/**
|
|
49
|
+
* Reads one entry. `TValue` is an unchecked assertion — it only shapes
|
|
50
|
+
* the return type; nothing verifies the stored value matches it.
|
|
51
|
+
*/
|
|
47
52
|
get<TValue = unknown>(key: string, options?: NamespaceOptions): Promise<CacheGetResult<TValue>>;
|
|
48
53
|
set<TValue = unknown>(key: string, value: TValue, options?: {
|
|
49
54
|
readonly ttl?: CacheTTL;
|
|
@@ -150,7 +155,9 @@ export declare class CacheService implements CacheHealthChecker {
|
|
|
150
155
|
/**
|
|
151
156
|
* Runs input validation. A rejection is recorded like an adapter failure
|
|
152
157
|
* (`getStats().errors`, `cache.error`) and rethrown: invalid input is a
|
|
153
|
-
* caller error, so `failSilently` never hides it.
|
|
158
|
+
* caller error, so `failSilently` never hides it. The key builder does
|
|
159
|
+
* not know which operation asked for the key, so the rejection is
|
|
160
|
+
* attributed to `operation` here rather than reported as `unknown`.
|
|
154
161
|
*/
|
|
155
162
|
private guardInput;
|
|
156
163
|
private purgeTagsMatching;
|
|
@@ -163,4 +170,3 @@ export declare function createCacheService(options: {
|
|
|
163
170
|
readonly keyBuilder?: CacheKeyBuilder;
|
|
164
171
|
}): CacheService;
|
|
165
172
|
export {};
|
|
166
|
-
//# sourceMappingURL=cache.d.ts.map
|
package/dist/cache.js
CHANGED
|
@@ -21,8 +21,8 @@ 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";
|
|
24
|
-
import { CacheError, cacheDeserializationError, cacheSerializationError, isCacheError, } from "./errors.js";
|
|
25
|
-
import { createGlobMatcher } from "./utils.js";
|
|
24
|
+
import { CacheError, CacheOperation, cacheDeserializationError, cacheSerializationError, isCacheError, } from "./errors.js";
|
|
25
|
+
import { attachCacheOperation, createGlobMatcher } from "./utils.js";
|
|
26
26
|
export class CacheService {
|
|
27
27
|
store;
|
|
28
28
|
keyBuilder;
|
|
@@ -85,10 +85,14 @@ export class CacheService {
|
|
|
85
85
|
});
|
|
86
86
|
this.lockManager = createLockManager(options.config?.lockStore ? { store: options.config.lockStore } : {});
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Reads one entry. `TValue` is an unchecked assertion — it only shapes
|
|
90
|
+
* the return type; nothing verifies the stored value matches it.
|
|
91
|
+
*/
|
|
88
92
|
async get(key, options) {
|
|
89
93
|
if (!this.enabled)
|
|
90
94
|
return { hit: false, value: null };
|
|
91
|
-
const fullKey = this.buildKey(key, options);
|
|
95
|
+
const fullKey = this.buildKey(key, options, CacheOperation.GET);
|
|
92
96
|
try {
|
|
93
97
|
const result = await this.store.get(fullKey);
|
|
94
98
|
if (!result.hit || !this.serializer)
|
|
@@ -111,9 +115,9 @@ export class CacheService {
|
|
|
111
115
|
async set(key, value, options) {
|
|
112
116
|
if (!this.enabled)
|
|
113
117
|
return { success: false, key, expiresAt: null };
|
|
114
|
-
const fullKey = this.buildKey(key, options);
|
|
118
|
+
const fullKey = this.buildKey(key, options, CacheOperation.SET);
|
|
115
119
|
if (options?.tags)
|
|
116
|
-
this.validateTags(key, options.tags);
|
|
120
|
+
this.validateTags(key, options.tags, CacheOperation.SET);
|
|
117
121
|
try {
|
|
118
122
|
const stored = this.serializer ? this.serialize(value) : value;
|
|
119
123
|
const result = await this.store.set(fullKey, stored, {
|
|
@@ -145,7 +149,7 @@ export class CacheService {
|
|
|
145
149
|
async delete(key, options) {
|
|
146
150
|
if (!this.enabled)
|
|
147
151
|
return { deleted: false, key };
|
|
148
|
-
const fullKey = this.buildKey(key, options);
|
|
152
|
+
const fullKey = this.buildKey(key, options, CacheOperation.DELETE);
|
|
149
153
|
try {
|
|
150
154
|
const result = await this.store.delete(fullKey);
|
|
151
155
|
await this.tagStore.removeKey?.(fullKey);
|
|
@@ -160,7 +164,7 @@ export class CacheService {
|
|
|
160
164
|
async has(key, options) {
|
|
161
165
|
if (!this.enabled)
|
|
162
166
|
return false;
|
|
163
|
-
const fullKey = this.buildKey(key, options);
|
|
167
|
+
const fullKey = this.buildKey(key, options, CacheOperation.HAS);
|
|
164
168
|
try {
|
|
165
169
|
return await this.store.has(fullKey);
|
|
166
170
|
}
|
|
@@ -184,7 +188,7 @@ export class CacheService {
|
|
|
184
188
|
if (options?.pattern !== undefined || options?.namespace !== undefined) {
|
|
185
189
|
// Validation happens outside the failSilently guard: a malformed
|
|
186
190
|
// pattern is programmer error, not an adapter fault.
|
|
187
|
-
const pattern = this.qualifyPattern(options.pattern ?? "*", options.namespace);
|
|
191
|
+
const pattern = this.qualifyPattern(options.pattern ?? "*", options.namespace, CacheOperation.CLEAR);
|
|
188
192
|
try {
|
|
189
193
|
const result = await this.store.clear({ pattern });
|
|
190
194
|
await this.purgeTagsMatching(pattern);
|
|
@@ -214,7 +218,7 @@ export class CacheService {
|
|
|
214
218
|
async ttl(key, options) {
|
|
215
219
|
if (!this.enabled)
|
|
216
220
|
return undefined;
|
|
217
|
-
const fullKey = this.buildKey(key, options);
|
|
221
|
+
const fullKey = this.buildKey(key, options, CacheOperation.TTL);
|
|
218
222
|
try {
|
|
219
223
|
const remaining = await this.store.ttl?.(fullKey);
|
|
220
224
|
return typeof remaining === "number" && Number.isFinite(remaining)
|
|
@@ -233,7 +237,7 @@ export class CacheService {
|
|
|
233
237
|
async expire(key, ttl, options) {
|
|
234
238
|
if (!this.enabled)
|
|
235
239
|
return false;
|
|
236
|
-
const fullKey = this.buildKey(key, options);
|
|
240
|
+
const fullKey = this.buildKey(key, options, CacheOperation.EXPIRE);
|
|
237
241
|
try {
|
|
238
242
|
return (await this.store.expire?.(fullKey, ttl)) ?? false;
|
|
239
243
|
}
|
|
@@ -246,7 +250,7 @@ export class CacheService {
|
|
|
246
250
|
async getOrSet(key, fn, options) {
|
|
247
251
|
if (!this.enabled)
|
|
248
252
|
return { value: await fn(), cached: false };
|
|
249
|
-
const fullKey = this.buildKey(key, options);
|
|
253
|
+
const fullKey = this.buildKey(key, options, CacheOperation.GET);
|
|
250
254
|
if (!options?.forceRefresh) {
|
|
251
255
|
const cached = await this.get(key, options);
|
|
252
256
|
if (cached.hit)
|
|
@@ -278,7 +282,7 @@ export class CacheService {
|
|
|
278
282
|
async invalidateByTag(tags, options) {
|
|
279
283
|
if (!this.enabled)
|
|
280
284
|
return { cleared: 0 };
|
|
281
|
-
this.validateTags(tags.join(","), tags);
|
|
285
|
+
this.validateTags(tags.join(","), tags, CacheOperation.DELETE_MANY);
|
|
282
286
|
try {
|
|
283
287
|
return await this.invalidation.invalidateByTag(tags, this.tagScope(options));
|
|
284
288
|
}
|
|
@@ -301,7 +305,7 @@ export class CacheService {
|
|
|
301
305
|
async invalidateByPattern(pattern, options) {
|
|
302
306
|
if (!this.enabled)
|
|
303
307
|
return { cleared: 0 };
|
|
304
|
-
const qualified = this.qualifyPattern(pattern, options?.namespace);
|
|
308
|
+
const qualified = this.qualifyPattern(pattern, options?.namespace, CacheOperation.DELETE_MANY);
|
|
305
309
|
try {
|
|
306
310
|
const result = await this.invalidation.invalidateByPattern(qualified);
|
|
307
311
|
await this.purgeTagsMatching(qualified);
|
|
@@ -332,7 +336,7 @@ export class CacheService {
|
|
|
332
336
|
}
|
|
333
337
|
const lockKey = this.buildKey(key, options?.namespace !== undefined
|
|
334
338
|
? { namespace: options.namespace }
|
|
335
|
-
: undefined);
|
|
339
|
+
: undefined, CacheOperation.LOCK_ACQUIRE);
|
|
336
340
|
return this.lockManager.withLock(lockKey, fn, {
|
|
337
341
|
...(options?.ttl !== undefined ? { ttl: options.ttl } : {}),
|
|
338
342
|
// `retryAttempts: 0` is honored (single attempt, no retries).
|
|
@@ -475,19 +479,19 @@ export class CacheService {
|
|
|
475
479
|
assertNonEmptyNamespace(namespace);
|
|
476
480
|
return namespace !== undefined ? { namespace } : {};
|
|
477
481
|
}
|
|
478
|
-
qualifyPattern(pattern, namespace) {
|
|
482
|
+
qualifyPattern(pattern, namespace, operation) {
|
|
479
483
|
const build = this.keyBuilder.buildPattern?.bind(this.keyBuilder);
|
|
480
484
|
if (!build)
|
|
481
485
|
return pattern;
|
|
482
|
-
return this.guardInput(pattern, () => build(pattern, namespace !== undefined ? { namespace } : undefined));
|
|
486
|
+
return this.guardInput(pattern, operation, () => build(pattern, namespace !== undefined ? { namespace } : undefined));
|
|
483
487
|
}
|
|
484
488
|
/** Builds a full key, counting a rejected key in the error stats. */
|
|
485
|
-
buildKey(key, options) {
|
|
486
|
-
return this.guardInput(key, () => this.keyBuilder.build(key, options));
|
|
489
|
+
buildKey(key, options, operation) {
|
|
490
|
+
return this.guardInput(key, operation, () => this.keyBuilder.build(key, options));
|
|
487
491
|
}
|
|
488
492
|
/** Validates tags, counting a rejected tag in the error stats. */
|
|
489
|
-
validateTags(key, tags) {
|
|
490
|
-
this.guardInput(key, () => {
|
|
493
|
+
validateTags(key, tags, operation) {
|
|
494
|
+
this.guardInput(key, operation, () => {
|
|
491
495
|
for (const tag of tags)
|
|
492
496
|
assertValidTag(tag);
|
|
493
497
|
});
|
|
@@ -495,15 +499,18 @@ export class CacheService {
|
|
|
495
499
|
/**
|
|
496
500
|
* Runs input validation. A rejection is recorded like an adapter failure
|
|
497
501
|
* (`getStats().errors`, `cache.error`) and rethrown: invalid input is a
|
|
498
|
-
* caller error, so `failSilently` never hides it.
|
|
502
|
+
* caller error, so `failSilently` never hides it. The key builder does
|
|
503
|
+
* not know which operation asked for the key, so the rejection is
|
|
504
|
+
* attributed to `operation` here rather than reported as `unknown`.
|
|
499
505
|
*/
|
|
500
|
-
guardInput(key, validate) {
|
|
506
|
+
guardInput(key, operation, validate) {
|
|
501
507
|
try {
|
|
502
508
|
return validate();
|
|
503
509
|
}
|
|
504
510
|
catch (error) {
|
|
505
|
-
|
|
506
|
-
|
|
511
|
+
const attributed = attachCacheOperation(error, operation);
|
|
512
|
+
this.store.recordError(key, attributed);
|
|
513
|
+
throw attributed;
|
|
507
514
|
}
|
|
508
515
|
}
|
|
509
516
|
async purgeTagsMatching(pattern) {
|
|
@@ -540,4 +547,3 @@ export class CacheService {
|
|
|
540
547
|
export function createCacheService(options) {
|
|
541
548
|
return new CacheService(options);
|
|
542
549
|
}
|
|
543
|
-
//# sourceMappingURL=cache.js.map
|
package/dist/constants.d.ts
CHANGED
|
@@ -19,18 +19,24 @@ export declare const DEFAULT_PREFIX = "zudojs";
|
|
|
19
19
|
/** Maximum key length in characters. */
|
|
20
20
|
export declare const MAX_KEY_LENGTH = 256;
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* Alphabet of one cache key part (prefix, namespace, and raw key): letters,
|
|
23
|
+
* digits, `.`, `_`, `-` and `:`.
|
|
24
|
+
*
|
|
25
|
+
* The *active separator* is rejected separately, so with the default `:`
|
|
26
|
+
* separator a part can still never contain `:` — `build("admin:x")` would
|
|
27
|
+
* otherwise be indistinguishable from key `x` in namespace `admin`, and a
|
|
28
|
+
* namespace-scoped invalidation would reach it. `:` inside a part is only
|
|
29
|
+
* possible under a different separator
|
|
30
|
+
* (`createKeyBuilder({ separator: "/" })` gives `zudojs/tenant:1/dashboard`),
|
|
31
|
+
* where it cannot collide with the scope structure.
|
|
26
32
|
*/
|
|
27
33
|
export declare const CACHE_KEY_PATTERN: RegExp;
|
|
28
34
|
/**
|
|
29
35
|
* Pattern used to validate the caller-supplied *glob* segment of a key
|
|
30
36
|
* pattern. It is the key alphabet plus the two glob metacharacters `*` and
|
|
31
37
|
* `?` — nothing else has meaning for the matcher, so anything else is
|
|
32
|
-
* caller error. The separator is
|
|
33
|
-
* prefix/namespace scope it is composed into.
|
|
38
|
+
* caller error. The active separator is rejected separately so a pattern
|
|
39
|
+
* cannot escape the prefix/namespace scope it is composed into.
|
|
34
40
|
*/
|
|
35
41
|
export declare const CACHE_PATTERN_PART_PATTERN: RegExp;
|
|
36
42
|
/** Maximum length of a cache tag. Tags are untrusted map keys. */
|
|
@@ -71,4 +77,3 @@ export declare const EXPIRED_PURGE_INTERVAL_MS = 30000;
|
|
|
71
77
|
* Keeps `set` O(1)-ish for large object graphs at the cost of accuracy.
|
|
72
78
|
*/
|
|
73
79
|
export declare const SIZE_ESTIMATE_NODE_BUDGET = 512;
|
|
74
|
-
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
|
@@ -25,20 +25,26 @@ export const DEFAULT_PREFIX = "zudojs";
|
|
|
25
25
|
/** Maximum key length in characters. */
|
|
26
26
|
export const MAX_KEY_LENGTH = 256;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Alphabet of one cache key part (prefix, namespace, and raw key): letters,
|
|
29
|
+
* digits, `.`, `_`, `-` and `:`.
|
|
30
|
+
*
|
|
31
|
+
* The *active separator* is rejected separately, so with the default `:`
|
|
32
|
+
* separator a part can still never contain `:` — `build("admin:x")` would
|
|
33
|
+
* otherwise be indistinguishable from key `x` in namespace `admin`, and a
|
|
34
|
+
* namespace-scoped invalidation would reach it. `:` inside a part is only
|
|
35
|
+
* possible under a different separator
|
|
36
|
+
* (`createKeyBuilder({ separator: "/" })` gives `zudojs/tenant:1/dashboard`),
|
|
37
|
+
* where it cannot collide with the scope structure.
|
|
32
38
|
*/
|
|
33
|
-
export const CACHE_KEY_PATTERN = /^[a-zA-Z0-9._
|
|
39
|
+
export const CACHE_KEY_PATTERN = /^[a-zA-Z0-9._:\-]+$/;
|
|
34
40
|
/**
|
|
35
41
|
* Pattern used to validate the caller-supplied *glob* segment of a key
|
|
36
42
|
* pattern. It is the key alphabet plus the two glob metacharacters `*` and
|
|
37
43
|
* `?` — nothing else has meaning for the matcher, so anything else is
|
|
38
|
-
* caller error. The separator is
|
|
39
|
-
* prefix/namespace scope it is composed into.
|
|
44
|
+
* caller error. The active separator is rejected separately so a pattern
|
|
45
|
+
* cannot escape the prefix/namespace scope it is composed into.
|
|
40
46
|
*/
|
|
41
|
-
export const CACHE_PATTERN_PART_PATTERN = /^[a-zA-Z0-9._
|
|
47
|
+
export const CACHE_PATTERN_PART_PATTERN = /^[a-zA-Z0-9._:\-*?]+$/;
|
|
42
48
|
/** Maximum length of a cache tag. Tags are untrusted map keys. */
|
|
43
49
|
export const MAX_TAG_LENGTH = 128;
|
|
44
50
|
/* -------------------------------------------------------------------------- */
|
|
@@ -86,4 +92,3 @@ export const EXPIRED_PURGE_INTERVAL_MS = 30_000;
|
|
|
86
92
|
* Keeps `set` O(1)-ish for large object graphs at the cost of accuracy.
|
|
87
93
|
*/
|
|
88
94
|
export const SIZE_ESTIMATE_NODE_BUDGET = 512;
|
|
89
|
-
//# sourceMappingURL=constants.js.map
|
package/dist/errors.d.ts
CHANGED
|
@@ -6,4 +6,3 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { CacheError, isCacheError, cacheConnectionError, cacheTimeoutError, cacheSerializationError, cacheDeserializationError, cacheInvalidKeyError, cacheAdapterNotConfiguredError, CacheOperation, } from "@zudojs/errors";
|
|
8
8
|
export type { CacheErrorOptions } from "@zudojs/errors";
|
|
9
|
-
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.js
CHANGED
|
@@ -5,4 +5,3 @@
|
|
|
5
5
|
* for convenience. No local error classes are created in this package.
|
|
6
6
|
*/
|
|
7
7
|
export { CacheError, isCacheError, cacheConnectionError, cacheTimeoutError, cacheSerializationError, cacheDeserializationError, cacheInvalidKeyError, cacheAdapterNotConfiguredError, CacheOperation, } from "@zudojs/errors";
|
|
8
|
-
//# sourceMappingURL=errors.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -33,4 +33,3 @@ export { CacheInvalidationManager, createInvalidationManager, } from "./invalida
|
|
|
33
33
|
export { InMemoryLockStore, CacheLockManager, createLockManager, defaultLockStore, } from "./lock.js";
|
|
34
34
|
export { InMemoryCacheMetrics, createCacheMetrics } from "./metrics.js";
|
|
35
35
|
export { CacheService, createCacheService } from "./cache.js";
|
|
36
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -40,4 +40,3 @@ export { InMemoryLockStore, CacheLockManager, createLockManager, defaultLockStor
|
|
|
40
40
|
export { InMemoryCacheMetrics, createCacheMetrics } from "./metrics.js";
|
|
41
41
|
// Cache Service
|
|
42
42
|
export { CacheService, createCacheService } from "./cache.js";
|
|
43
|
-
//# sourceMappingURL=index.js.map
|
package/dist/invalidation.d.ts
CHANGED
package/dist/invalidation.js
CHANGED
package/dist/key-builder.d.ts
CHANGED
package/dist/key-builder.js
CHANGED
|
@@ -104,7 +104,15 @@ export class DefaultKeyBuilder {
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
validatePart(part, separator) {
|
|
107
|
-
if (part.includes(separator)
|
|
107
|
+
if (part.includes(separator)) {
|
|
108
|
+
throw cacheInvalidKeyError(part, `Invalid cache key part "${part}": parts must not contain the ` +
|
|
109
|
+
`separator "${separator}", which delimits prefix, namespace and ` +
|
|
110
|
+
`key (a key "a${separator}b" would collide with key "b" in ` +
|
|
111
|
+
`namespace "a"). Put the scope in the namespace option ` +
|
|
112
|
+
`(cache.get("b", { namespace: "a" })), use "." or "-" inside ` +
|
|
113
|
+
`the part, or configure a different separator on the key builder.`);
|
|
114
|
+
}
|
|
115
|
+
if (!CACHE_KEY_PATTERN.test(part)) {
|
|
108
116
|
throw cacheInvalidKeyError(part, `Invalid cache key part "${part}": parts must match ${String(CACHE_KEY_PATTERN)} and must not contain the separator "${separator}".`);
|
|
109
117
|
}
|
|
110
118
|
}
|
|
@@ -120,4 +128,3 @@ export function createKeyBuilder(options) {
|
|
|
120
128
|
}
|
|
121
129
|
/** Default key builder singleton. */
|
|
122
130
|
export const defaultKeyBuilder = new DefaultKeyBuilder();
|
|
123
|
-
//# sourceMappingURL=key-builder.js.map
|
package/dist/lock.d.ts
CHANGED
package/dist/lock.js
CHANGED
package/dist/memory.d.ts
CHANGED
package/dist/memory.js
CHANGED
package/dist/metrics.d.ts
CHANGED
package/dist/metrics.js
CHANGED
package/dist/serializer.d.ts
CHANGED
|
@@ -53,4 +53,3 @@ export declare class RawCacheSerializer implements CacheSerializer<unknown, unkn
|
|
|
53
53
|
export declare const defaultSerializer: JsonCacheSerializer;
|
|
54
54
|
/** Default raw (pass-through) serializer instance. */
|
|
55
55
|
export declare const rawSerializer: RawCacheSerializer;
|
|
56
|
-
//# sourceMappingURL=serializer.d.ts.map
|
package/dist/serializer.js
CHANGED
package/dist/store.d.ts
CHANGED
package/dist/store.js
CHANGED
package/dist/tags.d.ts
CHANGED
package/dist/tags.js
CHANGED
package/dist/types-adapter.d.ts
CHANGED
|
@@ -13,6 +13,14 @@ export interface CacheAdapter {
|
|
|
13
13
|
readonly name: string;
|
|
14
14
|
connect?(): Promise<void>;
|
|
15
15
|
disconnect?(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Reads one entry.
|
|
18
|
+
*
|
|
19
|
+
* `TValue` appears only in the return type, so it is an **unchecked
|
|
20
|
+
* assertion** by the caller: nothing verifies that the stored value has
|
|
21
|
+
* that shape. Validate a value that crosses a trust boundary (a shared
|
|
22
|
+
* cache, a schema that changed between deploys) before relying on it.
|
|
23
|
+
*/
|
|
16
24
|
get<TValue = unknown>(key: CacheKey): Promise<CacheGetResult<TValue>>;
|
|
17
25
|
set<TValue = unknown>(key: CacheKey, value: TValue, options?: CacheSetOptions): Promise<CacheSetResult>;
|
|
18
26
|
delete(key: CacheKey): Promise<CacheDeleteResult>;
|
|
@@ -35,4 +43,3 @@ export interface CacheAdapter {
|
|
|
35
43
|
}
|
|
36
44
|
export interface CacheStore extends CacheAdapter {
|
|
37
45
|
}
|
|
38
|
-
//# sourceMappingURL=types-adapter.d.ts.map
|
package/dist/types-adapter.js
CHANGED
package/dist/types-config.d.ts
CHANGED
|
@@ -64,4 +64,3 @@ export interface CacheConfig {
|
|
|
64
64
|
* generic `ErrorCode` values, not these.
|
|
65
65
|
*/
|
|
66
66
|
export type CacheErrorCode = "CACHE_DISABLED" | "CACHE_OPERATION_FAILED" | "CACHE_INVALID_TTL" | "CACHE_MIDDLEWARE_RESULT_MISSING" | "CACHE_LOCK_UNAVAILABLE" | "CACHE_LOCK_ACQUIRE_FAILED" | "CACHE_LOCK_LOST";
|
|
67
|
-
//# sourceMappingURL=types-config.d.ts.map
|
package/dist/types-config.js
CHANGED
package/dist/types-events.d.ts
CHANGED
package/dist/types-events.js
CHANGED
package/dist/types-health.d.ts
CHANGED
package/dist/types-health.js
CHANGED
package/dist/types-keys.d.ts
CHANGED
package/dist/types-keys.js
CHANGED
package/dist/types-lock.d.ts
CHANGED
package/dist/types-lock.js
CHANGED
package/dist/types-metrics.d.ts
CHANGED
package/dist/types-metrics.js
CHANGED
package/dist/types-operations.js
CHANGED
package/dist/types-results.d.ts
CHANGED
package/dist/types-results.js
CHANGED
package/dist/types-tags.d.ts
CHANGED
package/dist/types-tags.js
CHANGED
package/dist/types-utility.d.ts
CHANGED
package/dist/types-utility.js
CHANGED
package/dist/types-values.d.ts
CHANGED
package/dist/types-values.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -10,4 +10,3 @@ export type { CacheHealth, CacheHealthChecker, CacheSerializer, CacheSerializati
|
|
|
10
10
|
export type { CacheMiddleware, CacheMiddlewareContext, CacheMetrics, CacheOperation, } from "./types-metrics.js";
|
|
11
11
|
export type { CacheConfig, CacheErrorCode } from "./types-config.js";
|
|
12
12
|
export type { CacheBatchOperation, CacheBatchResult, CacheOrComputeOptions, CacheOrComputeResult, MaybePromise, } from "./types-utility.js";
|
|
13
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
CHANGED
package/dist/utils.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* TTL/pattern validation, and a monotonic clock.
|
|
6
6
|
*/
|
|
7
7
|
import type { CacheDeleteManyResult, CacheDeleteResult, CacheKey, CacheTTL } from "./types.js";
|
|
8
|
+
import { CacheOperation } from "./errors.js";
|
|
8
9
|
/**
|
|
9
10
|
* Monotonic milliseconds since process start. Unlike `Date.now()` this is
|
|
10
11
|
* unaffected by NTP steps, VM resume, or manual clock changes, so deadlines
|
|
@@ -62,4 +63,11 @@ export declare function deleteManyViaDelete(keys: readonly CacheKey[], deleteOne
|
|
|
62
63
|
* `CACHE_INVALID_TTL` code otherwise.
|
|
63
64
|
*/
|
|
64
65
|
export declare function assertValidTtl(ttl: CacheTTL | undefined): void;
|
|
65
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Returns `error` with its `operation` filled in when it is a `CacheError`
|
|
68
|
+
* raised before the operation was known — key, namespace, pattern and tag
|
|
69
|
+
* validation happens in the key builder, which has no idea whether a
|
|
70
|
+
* `get` or a `set` asked for the key. Anything else is returned unchanged.
|
|
71
|
+
* The original stack is kept so the throw site stays visible.
|
|
72
|
+
*/
|
|
73
|
+
export declare function attachCacheOperation(error: unknown, operation: CacheOperation): unknown;
|
package/dist/utils.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* TTL/pattern validation, and a monotonic clock.
|
|
6
6
|
*/
|
|
7
7
|
import { CACHE_PATTERN_PART_PATTERN, DEFAULT_SEPARATOR, MAX_KEY_LENGTH, MAX_TTL_MS, MIN_TTL_MS, } from "./constants.js";
|
|
8
|
-
import { CacheError, cacheInvalidKeyError } from "./errors.js";
|
|
8
|
+
import { CacheError, CacheOperation, cacheInvalidKeyError, isCacheError, } from "./errors.js";
|
|
9
9
|
/* -------------------------------------------------------------------------- */
|
|
10
10
|
/* Monotonic clock */
|
|
11
11
|
/* -------------------------------------------------------------------------- */
|
|
@@ -164,7 +164,12 @@ export function assertValidPatternPart(part, separator) {
|
|
|
164
164
|
if (part.length === 0) {
|
|
165
165
|
throw cacheInvalidKeyError(part, "Cache pattern must not be empty.");
|
|
166
166
|
}
|
|
167
|
-
if (part.includes(separator)
|
|
167
|
+
if (part.includes(separator)) {
|
|
168
|
+
throw cacheInvalidKeyError(part, `Invalid cache pattern "${part}": patterns must not contain the ` +
|
|
169
|
+
`separator "${separator}"; scope a pattern with the namespace ` +
|
|
170
|
+
`option instead of writing the namespace into it.`);
|
|
171
|
+
}
|
|
172
|
+
if (!CACHE_PATTERN_PART_PATTERN.test(part)) {
|
|
168
173
|
throw cacheInvalidKeyError(part, `Invalid cache pattern "${part}": patterns must match ${String(CACHE_PATTERN_PART_PATTERN)} and must not contain the separator "${separator}".`);
|
|
169
174
|
}
|
|
170
175
|
}
|
|
@@ -207,4 +212,30 @@ export function assertValidTtl(ttl) {
|
|
|
207
212
|
});
|
|
208
213
|
}
|
|
209
214
|
}
|
|
210
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Returns `error` with its `operation` filled in when it is a `CacheError`
|
|
217
|
+
* raised before the operation was known — key, namespace, pattern and tag
|
|
218
|
+
* validation happens in the key builder, which has no idea whether a
|
|
219
|
+
* `get` or a `set` asked for the key. Anything else is returned unchanged.
|
|
220
|
+
* The original stack is kept so the throw site stays visible.
|
|
221
|
+
*/
|
|
222
|
+
export function attachCacheOperation(error, operation) {
|
|
223
|
+
if (!isCacheError(error) || error.operation !== CacheOperation.UNKNOWN) {
|
|
224
|
+
return error;
|
|
225
|
+
}
|
|
226
|
+
const attributed = new CacheError(error.message, {
|
|
227
|
+
code: error.code,
|
|
228
|
+
category: error.category,
|
|
229
|
+
severity: error.severity,
|
|
230
|
+
operation,
|
|
231
|
+
key: error.key,
|
|
232
|
+
adapter: error.adapter,
|
|
233
|
+
statusCode: error.statusCode,
|
|
234
|
+
expose: error.expose,
|
|
235
|
+
isOperational: error.isOperational,
|
|
236
|
+
metadata: error.metadata,
|
|
237
|
+
cause: error.cause,
|
|
238
|
+
});
|
|
239
|
+
attributed.stack = error.stack;
|
|
240
|
+
return attributed;
|
|
241
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/cache",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Caching primitives, abstractions, and adapters for the Zudojs framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"!dist/.tsbuildinfo"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zudojs/constants": "1.
|
|
28
|
-
"@zudojs/errors": "1.
|
|
29
|
-
"@zudojs/serialization": "1.
|
|
30
|
-
"@zudojs/types": "1.
|
|
27
|
+
"@zudojs/constants": "1.2.0",
|
|
28
|
+
"@zudojs/errors": "1.4.0",
|
|
29
|
+
"@zudojs/serialization": "1.3.0",
|
|
30
|
+
"@zudojs/types": "1.3.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"typescript": "7.0.2",
|