inray 0.0.2 → 0.0.3
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.md +1 -0
- package/README.md +132 -32
- package/dist/caching/mod.d.ts +2 -0
- package/dist/caching/mod.d.ts.map +1 -0
- package/dist/caching/mod.js +2 -0
- package/dist/caching/mod.js.map +1 -0
- package/dist/caching/src/cache.d.ts +89 -0
- package/dist/caching/src/cache.d.ts.map +1 -0
- package/dist/caching/src/cache.js +309 -0
- package/dist/caching/src/cache.js.map +1 -0
- package/dist/caching/src/hashing/cache.d.ts +92 -0
- package/dist/caching/src/hashing/cache.d.ts.map +1 -0
- package/dist/caching/src/hashing/cache.js +119 -0
- package/dist/caching/src/hashing/cache.js.map +1 -0
- package/dist/caching/src/hashing/hooks/eventified.d.ts +157 -0
- package/dist/caching/src/hashing/hooks/eventified.d.ts.map +1 -0
- package/dist/caching/src/hashing/hooks/eventified.js +333 -0
- package/dist/caching/src/hashing/hooks/eventified.js.map +1 -0
- package/dist/caching/src/hashing/hooks/index.d.ts +199 -0
- package/dist/caching/src/hashing/hooks/index.d.ts.map +1 -0
- package/dist/caching/src/hashing/hooks/index.js +383 -0
- package/dist/caching/src/hashing/hooks/index.js.map +1 -0
- package/dist/caching/src/hashing/hooks/types.d.ts +217 -0
- package/dist/caching/src/hashing/hooks/types.d.ts.map +1 -0
- package/dist/caching/src/hashing/hooks/types.js +10 -0
- package/dist/caching/src/hashing/hooks/types.js.map +1 -0
- package/dist/caching/src/hashing/index.d.ts +236 -0
- package/dist/caching/src/hashing/index.d.ts.map +1 -0
- package/dist/caching/src/hashing/index.js +447 -0
- package/dist/caching/src/hashing/index.js.map +1 -0
- package/dist/caching/src/hashing/providers/crc.d.ts +15 -0
- package/dist/caching/src/hashing/providers/crc.d.ts.map +1 -0
- package/dist/caching/src/hashing/providers/crc.js +49 -0
- package/dist/caching/src/hashing/providers/crc.js.map +1 -0
- package/dist/caching/src/hashing/providers/crypto.d.ts +19 -0
- package/dist/caching/src/hashing/providers/crypto.d.ts.map +1 -0
- package/dist/caching/src/hashing/providers/crypto.js +28 -0
- package/dist/caching/src/hashing/providers/crypto.js.map +1 -0
- package/dist/caching/src/hashing/providers/djb2.d.ts +60 -0
- package/dist/caching/src/hashing/providers/djb2.d.ts.map +1 -0
- package/dist/caching/src/hashing/providers/djb2.js +92 -0
- package/dist/caching/src/hashing/providers/djb2.js.map +1 -0
- package/dist/caching/src/hashing/providers/fnv1.d.ts +33 -0
- package/dist/caching/src/hashing/providers/fnv1.d.ts.map +1 -0
- package/dist/caching/src/hashing/providers/fnv1.js +68 -0
- package/dist/caching/src/hashing/providers/fnv1.js.map +1 -0
- package/dist/caching/src/hashing/providers/murmur.d.ts +81 -0
- package/dist/caching/src/hashing/providers/murmur.d.ts.map +1 -0
- package/dist/caching/src/hashing/providers/murmur.js +173 -0
- package/dist/caching/src/hashing/providers/murmur.js.map +1 -0
- package/dist/caching/src/hashing/providers.d.ts +126 -0
- package/dist/caching/src/hashing/providers.d.ts.map +1 -0
- package/dist/caching/src/hashing/providers.js +163 -0
- package/dist/caching/src/hashing/providers.js.map +1 -0
- package/dist/caching/src/hashing/types.d.ts +340 -0
- package/dist/caching/src/hashing/types.d.ts.map +1 -0
- package/dist/caching/src/hashing/types.js +10 -0
- package/dist/caching/src/hashing/types.js.map +1 -0
- package/dist/caching/src/keyValue/capabilities.d.ts +161 -0
- package/dist/caching/src/keyValue/capabilities.d.ts.map +1 -0
- package/dist/caching/src/keyValue/capabilities.js +334 -0
- package/dist/caching/src/keyValue/capabilities.js.map +1 -0
- package/dist/caching/src/keyValue/event-manager.d.ts +27 -0
- package/dist/caching/src/keyValue/event-manager.d.ts.map +1 -0
- package/dist/caching/src/keyValue/event-manager.js +88 -0
- package/dist/caching/src/keyValue/event-manager.js.map +1 -0
- package/dist/caching/src/keyValue/generic-store.d.ts +243 -0
- package/dist/caching/src/keyValue/generic-store.d.ts.map +1 -0
- package/dist/caching/src/keyValue/generic-store.js +295 -0
- package/dist/caching/src/keyValue/generic-store.js.map +1 -0
- package/dist/caching/src/keyValue/hooks-manager.d.ts +21 -0
- package/dist/caching/src/keyValue/hooks-manager.d.ts.map +1 -0
- package/dist/caching/src/keyValue/hooks-manager.js +59 -0
- package/dist/caching/src/keyValue/hooks-manager.js.map +1 -0
- package/dist/caching/src/keyValue/index.d.ts +334 -0
- package/dist/caching/src/keyValue/index.d.ts.map +1 -0
- package/dist/caching/src/keyValue/index.js +796 -0
- package/dist/caching/src/keyValue/index.js.map +1 -0
- package/dist/caching/src/keyValue/serialize/serialize.d.ts +12 -0
- package/dist/caching/src/keyValue/serialize/serialize.d.ts.map +1 -0
- package/dist/caching/src/keyValue/serialize/serialize.js +64 -0
- package/dist/caching/src/keyValue/serialize/serialize.js.map +1 -0
- package/dist/caching/src/keyValue/stats-manager.d.ts +27 -0
- package/dist/caching/src/keyValue/stats-manager.d.ts.map +1 -0
- package/dist/caching/src/keyValue/stats-manager.js +64 -0
- package/dist/caching/src/keyValue/stats-manager.js.map +1 -0
- package/dist/caching/src/keyValue/types.d.ts +22 -0
- package/dist/caching/src/keyValue/types.d.ts.map +1 -0
- package/dist/caching/src/keyValue/types.js +11 -0
- package/dist/caching/src/keyValue/types.js.map +1 -0
- package/dist/caching/src/keyv-adapter.d.ts +40 -0
- package/dist/caching/src/keyv-adapter.d.ts.map +1 -0
- package/dist/caching/src/keyv-adapter.js +56 -0
- package/dist/caching/src/keyv-adapter.js.map +1 -0
- package/dist/caching/src/utils/cache-tags.d.ts +275 -0
- package/dist/caching/src/utils/cache-tags.d.ts.map +1 -0
- package/dist/caching/src/utils/cache-tags.js +407 -0
- package/dist/caching/src/utils/cache-tags.js.map +1 -0
- package/dist/caching/src/utils/cacheable-item-types.d.ts +35 -0
- package/dist/caching/src/utils/cacheable-item-types.d.ts.map +1 -0
- package/dist/caching/src/utils/cacheable-item-types.js +10 -0
- package/dist/caching/src/utils/cacheable-item-types.js.map +1 -0
- package/dist/caching/src/utils/coalesce-async.d.ts +29 -0
- package/dist/caching/src/utils/coalesce-async.d.ts.map +1 -0
- package/dist/caching/src/utils/coalesce-async.js +88 -0
- package/dist/caching/src/utils/coalesce-async.js.map +1 -0
- package/dist/caching/src/utils/hash.d.ts +55 -0
- package/dist/caching/src/utils/hash.d.ts.map +1 -0
- package/dist/caching/src/utils/hash.js +116 -0
- package/dist/caching/src/utils/hash.js.map +1 -0
- package/dist/caching/src/utils/index.d.ts +24 -0
- package/dist/caching/src/utils/index.d.ts.map +1 -0
- package/dist/caching/src/utils/index.js +21 -0
- package/dist/caching/src/utils/index.js.map +1 -0
- package/dist/caching/src/utils/is-keyv-instance.d.ts +10 -0
- package/dist/caching/src/utils/is-keyv-instance.d.ts.map +1 -0
- package/dist/caching/src/utils/is-keyv-instance.js +37 -0
- package/dist/caching/src/utils/is-keyv-instance.js.map +1 -0
- package/dist/caching/src/utils/is-object.d.ts +10 -0
- package/dist/caching/src/utils/is-object.d.ts.map +1 -0
- package/dist/caching/src/utils/is-object.js +13 -0
- package/dist/caching/src/utils/is-object.js.map +1 -0
- package/dist/caching/src/utils/less-than.d.ts +10 -0
- package/dist/caching/src/utils/less-than.d.ts.map +1 -0
- package/dist/caching/src/utils/less-than.js +13 -0
- package/dist/caching/src/utils/less-than.js.map +1 -0
- package/dist/caching/src/utils/memoize.d.ts +107 -0
- package/dist/caching/src/utils/memoize.d.ts.map +1 -0
- package/dist/caching/src/utils/memoize.js +201 -0
- package/dist/caching/src/utils/memoize.js.map +1 -0
- package/dist/caching/src/utils/mock-cacheable.d.ts +28 -0
- package/dist/caching/src/utils/mock-cacheable.d.ts.map +1 -0
- package/dist/caching/src/utils/mock-cacheable.js +87 -0
- package/dist/caching/src/utils/mock-cacheable.js.map +1 -0
- package/dist/caching/src/utils/run-if-fn.d.ts +12 -0
- package/dist/caching/src/utils/run-if-fn.d.ts.map +1 -0
- package/dist/caching/src/utils/run-if-fn.js +12 -0
- package/dist/caching/src/utils/run-if-fn.js.map +1 -0
- package/dist/caching/src/utils/shorthand-time.d.ts +27 -0
- package/dist/caching/src/utils/shorthand-time.d.ts.map +1 -0
- package/dist/caching/src/utils/shorthand-time.js +96 -0
- package/dist/caching/src/utils/shorthand-time.js.map +1 -0
- package/dist/caching/src/utils/sleep.d.ts +10 -0
- package/dist/caching/src/utils/sleep.d.ts.map +1 -0
- package/dist/caching/src/utils/sleep.js +11 -0
- package/dist/caching/src/utils/sleep.js.map +1 -0
- package/dist/caching/src/utils/stats.d.ts +354 -0
- package/dist/caching/src/utils/stats.d.ts.map +1 -0
- package/dist/caching/src/utils/stats.js +593 -0
- package/dist/caching/src/utils/stats.js.map +1 -0
- package/dist/caching/src/utils/ttl.d.ts +58 -0
- package/dist/caching/src/utils/ttl.d.ts.map +1 -0
- package/dist/caching/src/utils/ttl.js +77 -0
- package/dist/caching/src/utils/ttl.js.map +1 -0
- package/package.json +11 -3
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Original library: @cacheable/utils (v2.4.2)
|
|
3
|
+
* - Copyright (c) Jared Wray
|
|
4
|
+
*
|
|
5
|
+
* Modified and integrated in "Inray":
|
|
6
|
+
* - Evgeniy Bushin
|
|
7
|
+
*/
|
|
8
|
+
/***/
|
|
9
|
+
import type { Keyv } from '../keyValue/index.ts';
|
|
10
|
+
/**
|
|
11
|
+
* Options for constructing a {@link CacheTags}.
|
|
12
|
+
* @typedef {Object} CacheTagsOptions
|
|
13
|
+
* @property {Keyv} store - The Keyv store used to persist tag versions and key snapshots.
|
|
14
|
+
* @property {string} [namespace] - An optional namespace that isolates this service's tags
|
|
15
|
+
* and keys from others sharing the same store. Defaults to `"default"`.
|
|
16
|
+
* @property {boolean} [enabled] - Whether the service is enabled. While disabled, every method
|
|
17
|
+
* is a no-op: read methods return their neutral value ({@link CacheTags.isKeyFresh} returns
|
|
18
|
+
* `true`, {@link CacheTags.isKeyStale} returns `false`, etc.) and writes are skipped. The
|
|
19
|
+
* service must be explicitly enabled to use tags. Defaults to `true`.
|
|
20
|
+
* @property {(error: unknown) => void} [onError] - Invoked with errors from non-blocking
|
|
21
|
+
* (fire-and-forget) operations, which cannot be thrown to the caller. Defaults to ignoring them.
|
|
22
|
+
*/
|
|
23
|
+
export type CacheTagsOptions = {
|
|
24
|
+
store: Keyv;
|
|
25
|
+
namespace?: string;
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
onError?: (error: unknown) => void;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Options for {@link CacheTags.setKeyTags}.
|
|
31
|
+
* @typedef {Object} SetKeyTagsOptions
|
|
32
|
+
* @property {number} [ttl] - Time-to-live in milliseconds for the key's tag snapshot. Should
|
|
33
|
+
* match the TTL of the cached value it tracks so the snapshot expires alongside it. If omitted,
|
|
34
|
+
* the snapshot does not expire.
|
|
35
|
+
* @property {boolean} [nonBlocking] - When `true`, the snapshot write is fire-and-forget:
|
|
36
|
+
* the call resolves immediately and failures are reported via the `onError` option.
|
|
37
|
+
*/
|
|
38
|
+
export type SetKeyTagsOptions = {
|
|
39
|
+
ttl?: number;
|
|
40
|
+
nonBlocking?: boolean;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Options for {@link CacheTags.removeKey} and {@link CacheTags.removeKeys}.
|
|
44
|
+
* @typedef {Object} RemoveKeysOptions
|
|
45
|
+
* @property {boolean} [nonBlocking] - When `true`, the removal is fire-and-forget:
|
|
46
|
+
* the call resolves immediately and failures are reported via the `onError` option.
|
|
47
|
+
*/
|
|
48
|
+
export type RemoveKeysOptions = {
|
|
49
|
+
nonBlocking?: boolean;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The metadata stored for a tagged key. It records the version of each tag at the moment the key
|
|
53
|
+
* was written, allowing {@link CacheTags.isKeyFresh} to detect later invalidations.
|
|
54
|
+
* @typedef {Object} KeyTagEntry
|
|
55
|
+
* @property {Record<string, number>} tags - A snapshot mapping each tag name to its version at set time.
|
|
56
|
+
*/
|
|
57
|
+
export type KeyTagEntry = {
|
|
58
|
+
tags: Record<string, number>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Provides tag-based cache invalidation on top of any {@link Keyv} store. It is store-agnostic and
|
|
62
|
+
* requires no adapter changes.
|
|
63
|
+
*
|
|
64
|
+
* The service uses a lazy invalidation model rather than scanning and deleting keys. Each tag has a
|
|
65
|
+
* monotonically increasing version counter; {@link CacheTags.invalidateTag} simply increments
|
|
66
|
+
* it. When a key is tagged via {@link CacheTags.setKeyTags}, a snapshot of its tags' current
|
|
67
|
+
* versions is stored alongside it. {@link CacheTags.isKeyFresh} compares that snapshot against
|
|
68
|
+
* the live versions — if any tag has been incremented since, the key is considered stale. Stale
|
|
69
|
+
* entries are not deleted explicitly; they are expected to fall out of the cache via their TTL.
|
|
70
|
+
*
|
|
71
|
+
* This keeps invalidation constant-time regardless of how many keys reference a tag, at the cost of
|
|
72
|
+
* one additional `isKeyFresh` read per cache lookup.
|
|
73
|
+
*
|
|
74
|
+
* The service can be disabled via the `enabled` option or property so integrations pay no cost for
|
|
75
|
+
* untagged workloads: while disabled, every method is a no-op — reads return their neutral value
|
|
76
|
+
* and writes are skipped. The service must be explicitly enabled to use tags; it never enables
|
|
77
|
+
* itself, which keeps behavior consistent across distributed instances sharing a store.
|
|
78
|
+
*
|
|
79
|
+
* All metadata is written under a reserved prefix so it cannot collide with user keys:
|
|
80
|
+
* - `--cacheable--tags--:<namespace>:tag:<tag>` → integer version counter (stored without TTL).
|
|
81
|
+
* - `--cacheable--tags--:<namespace>:key:<key>` → the {@link KeyTagEntry} snapshot.
|
|
82
|
+
*
|
|
83
|
+
* Note: the read-version-then-write-snapshot sequence in `setKeyTags` is not atomic across
|
|
84
|
+
* processes. A concurrent `invalidateTag` running between the read and the write can leave a freshly
|
|
85
|
+
* written key referencing a stale version.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const cacheTags = new CacheTags({ store: new Keyv(), namespace: 'app' });
|
|
90
|
+
* await cacheTags.setKeyTags('user:42', ['users', 'org:7'], { ttl: 3600000 });
|
|
91
|
+
* await cacheTags.isKeyFresh('user:42'); // true
|
|
92
|
+
* await cacheTags.invalidateTag('users');
|
|
93
|
+
* await cacheTags.isKeyFresh('user:42'); // false
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare class CacheTags {
|
|
97
|
+
private readonly _store;
|
|
98
|
+
private readonly _namespace;
|
|
99
|
+
private _enabled;
|
|
100
|
+
private readonly _onError?;
|
|
101
|
+
/**
|
|
102
|
+
* Creates a new tag service.
|
|
103
|
+
* @param {CacheTagsOptions} options - The store, optional namespace, enabled state, and
|
|
104
|
+
* non-blocking error handler to use.
|
|
105
|
+
*/
|
|
106
|
+
constructor(options: CacheTagsOptions);
|
|
107
|
+
/**
|
|
108
|
+
* The Keyv store backing this service.
|
|
109
|
+
* @returns {Keyv} The store provided to the constructor.
|
|
110
|
+
*/
|
|
111
|
+
get store(): Keyv;
|
|
112
|
+
/**
|
|
113
|
+
* The namespace isolating this service's tags and keys within the store.
|
|
114
|
+
* @returns {string} The configured namespace, or `"default"` if none was provided.
|
|
115
|
+
*/
|
|
116
|
+
get namespace(): string;
|
|
117
|
+
/**
|
|
118
|
+
* Whether the service is enabled. While disabled, every method is a no-op — read methods
|
|
119
|
+
* return their neutral value and writes are skipped — so integrations pay no extra store
|
|
120
|
+
* reads for untagged workloads. The service must be explicitly enabled to use tags; it never
|
|
121
|
+
* enables itself.
|
|
122
|
+
* @returns {boolean} Whether the service is enabled.
|
|
123
|
+
*/
|
|
124
|
+
get enabled(): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Sets whether the service is enabled.
|
|
127
|
+
* @param {boolean} enabled Whether the service is enabled.
|
|
128
|
+
*/
|
|
129
|
+
set enabled(enabled: boolean);
|
|
130
|
+
/**
|
|
131
|
+
* Builds the reserved store key under which a tag's version counter is stored.
|
|
132
|
+
* @param tag - The tag name.
|
|
133
|
+
* @returns {string} The namespaced store key for the tag's version.
|
|
134
|
+
*/
|
|
135
|
+
private tagKey;
|
|
136
|
+
/**
|
|
137
|
+
* Builds the reserved store key under which a cache key's tag snapshot is stored.
|
|
138
|
+
* @param key - The cache key being tagged.
|
|
139
|
+
* @returns {string} The namespaced store key for the key's snapshot.
|
|
140
|
+
*/
|
|
141
|
+
private keyEntryKey;
|
|
142
|
+
/**
|
|
143
|
+
* Builds the common prefix shared by every key-snapshot entry in this namespace. Used to filter
|
|
144
|
+
* key entries when iterating the store.
|
|
145
|
+
* @returns {string} The namespaced key-entry prefix.
|
|
146
|
+
*/
|
|
147
|
+
private keyPrefix;
|
|
148
|
+
/**
|
|
149
|
+
* Reads the current version of a single tag.
|
|
150
|
+
* @param tag - The tag name.
|
|
151
|
+
* @returns {Promise<number>} The tag's version, or `0` if it has never been invalidated.
|
|
152
|
+
*/
|
|
153
|
+
private getTagVersion;
|
|
154
|
+
/**
|
|
155
|
+
* Reads the current versions of multiple tags in a single batched store read.
|
|
156
|
+
* @param tags - The tag names to look up.
|
|
157
|
+
* @returns {Promise<number[]>} The versions in the same order as `tags`; entries that have never
|
|
158
|
+
* been invalidated resolve to `0`. Returns an empty array when `tags` is empty.
|
|
159
|
+
*/
|
|
160
|
+
private getTagVersions;
|
|
161
|
+
/**
|
|
162
|
+
* Reports a fire-and-forget failure to the `onError` handler, if one was provided.
|
|
163
|
+
* @param error - The error raised by the non-blocking operation.
|
|
164
|
+
*/
|
|
165
|
+
private handleNonBlockingError;
|
|
166
|
+
/**
|
|
167
|
+
* Reads the version snapshot of each tag and writes the key's tag snapshot to the store.
|
|
168
|
+
* @param key - The cache key to tag.
|
|
169
|
+
* @param tags - The tags to associate with the key.
|
|
170
|
+
* @param ttl - Time-to-live in milliseconds for the snapshot.
|
|
171
|
+
* @returns {Promise<void>} Resolves once the snapshot has been written.
|
|
172
|
+
*/
|
|
173
|
+
private writeKeyTags;
|
|
174
|
+
/**
|
|
175
|
+
* Associates a cache key with a set of tags by recording a snapshot of each tag's current
|
|
176
|
+
* version. Call this whenever you write a fresh value to the cache. Duplicate tags are ignored.
|
|
177
|
+
* No-op while the service is disabled.
|
|
178
|
+
* @param key - The cache key to tag.
|
|
179
|
+
* @param tags - The tags to associate with the key.
|
|
180
|
+
* @param {SetKeyTagsOptions} [options] - Optional settings, such as a `ttl` for the snapshot or
|
|
181
|
+
* `nonBlocking` to fire-and-forget the write.
|
|
182
|
+
* @returns {Promise<void>} Resolves once the snapshot has been written, or immediately when
|
|
183
|
+
* `nonBlocking` is set.
|
|
184
|
+
*/
|
|
185
|
+
setKeyTags(key: string, tags: string[], options?: SetKeyTagsOptions): Promise<void>;
|
|
186
|
+
/**
|
|
187
|
+
* Removes a key's tag snapshot. After this, {@link CacheTags.isKeyFresh} returns `false`
|
|
188
|
+
* for the key. Use when the cached value itself is deleted. No-op while the service is
|
|
189
|
+
* disabled.
|
|
190
|
+
* @param key - The cache key whose snapshot should be removed.
|
|
191
|
+
* @param {RemoveKeysOptions} [options] - Optional settings, such as `nonBlocking` to
|
|
192
|
+
* fire-and-forget the removal.
|
|
193
|
+
* @returns {Promise<void>} Resolves once the snapshot has been deleted, or immediately when
|
|
194
|
+
* `nonBlocking` is set.
|
|
195
|
+
*/
|
|
196
|
+
removeKey(key: string, options?: RemoveKeysOptions): Promise<void>;
|
|
197
|
+
/**
|
|
198
|
+
* Removes multiple keys' tag snapshots in a single batched store delete. After this,
|
|
199
|
+
* {@link CacheTags.isKeyFresh} returns `false` for each key. An empty list is a no-op, as is
|
|
200
|
+
* the entire call while the service is disabled.
|
|
201
|
+
* @param keys - The cache keys whose snapshots should be removed.
|
|
202
|
+
* @param {RemoveKeysOptions} [options] - Optional settings, such as `nonBlocking` to
|
|
203
|
+
* fire-and-forget the removal.
|
|
204
|
+
* @returns {Promise<void>} Resolves once the snapshots have been deleted, or immediately when
|
|
205
|
+
* `nonBlocking` is set.
|
|
206
|
+
*/
|
|
207
|
+
removeKeys(keys: string[], options?: RemoveKeysOptions): Promise<void>;
|
|
208
|
+
/**
|
|
209
|
+
* Determines whether a key's cached value can still be trusted. A key is fresh only when a
|
|
210
|
+
* snapshot exists for it and every tag in that snapshot still has the version it had at set time.
|
|
211
|
+
* A key with no tags is trivially fresh. Call this before returning a value from your cache.
|
|
212
|
+
* Always returns `true` while the service is disabled.
|
|
213
|
+
* @param key - The cache key to check.
|
|
214
|
+
* @returns {Promise<boolean>} `true` if the key is still fresh; `false` if it is unknown or any of
|
|
215
|
+
* its tags has been invalidated since the snapshot was taken.
|
|
216
|
+
*/
|
|
217
|
+
isKeyFresh(key: string): Promise<boolean>;
|
|
218
|
+
/**
|
|
219
|
+
* Determines whether a key's cached value is known to be stale due to tag invalidation. This is
|
|
220
|
+
* the complement of {@link CacheTags.isKeyFresh} for tagged keys, but treats keys without a
|
|
221
|
+
* snapshot as not stale — making it safe to call for every cache lookup, including keys that were
|
|
222
|
+
* never tagged. Always returns `false` while the service is disabled.
|
|
223
|
+
* @param key - The cache key to check.
|
|
224
|
+
* @returns {Promise<boolean>} `true` only when a snapshot exists for the key and at least one of
|
|
225
|
+
* its tags has been invalidated since the snapshot was taken; `false` otherwise (including when
|
|
226
|
+
* the key has no snapshot).
|
|
227
|
+
*/
|
|
228
|
+
isKeyStale(key: string): Promise<boolean>;
|
|
229
|
+
/**
|
|
230
|
+
* Determines which of the given keys are known to be stale due to tag invalidation, using two
|
|
231
|
+
* batched store reads regardless of how many keys are checked: one for the snapshots and one for
|
|
232
|
+
* the union of their tag versions. Keys without a snapshot are not considered stale. Returns an
|
|
233
|
+
* empty array while the service is disabled.
|
|
234
|
+
* @param keys - The cache keys to check.
|
|
235
|
+
* @returns {Promise<string[]>} The subset of `keys` whose snapshot references at least one tag
|
|
236
|
+
* that has been invalidated since the snapshot was taken.
|
|
237
|
+
*/
|
|
238
|
+
getStaleKeys(keys: string[]): Promise<string[]>;
|
|
239
|
+
/**
|
|
240
|
+
* Returns the tags currently associated with a key. Returns `undefined` while the service is
|
|
241
|
+
* disabled.
|
|
242
|
+
* @param key - The cache key to look up.
|
|
243
|
+
* @returns {Promise<string[] | undefined>} The tag names from the key's snapshot, or `undefined`
|
|
244
|
+
* if the key has no snapshot.
|
|
245
|
+
*/
|
|
246
|
+
getTags(key: string): Promise<string[] | undefined>;
|
|
247
|
+
/**
|
|
248
|
+
* Returns all cache keys whose snapshot references the given tag. This scans every key entry in
|
|
249
|
+
* the namespace via the Keyv iterator, making it an `O(N)` operation intended for debugging and
|
|
250
|
+
* tests rather than hot paths. Returns an empty array if the underlying store exposes no iterator
|
|
251
|
+
* or while the service is disabled.
|
|
252
|
+
* @param tag - The tag to search for.
|
|
253
|
+
* @returns {Promise<string[]>} The cache keys (with the reserved prefix stripped) referencing the tag.
|
|
254
|
+
*/
|
|
255
|
+
getKeysByTag(tag: string): Promise<string[]>;
|
|
256
|
+
/**
|
|
257
|
+
* Invalidates a single tag by incrementing its version counter. Every key whose snapshot
|
|
258
|
+
* references this tag becomes stale immediately. Runs in constant time regardless of how many
|
|
259
|
+
* keys reference the tag. No-op while the service is disabled.
|
|
260
|
+
* @param tag - The tag to invalidate.
|
|
261
|
+
* @returns {Promise<string[]>} A single-element array containing the invalidated tag, or an
|
|
262
|
+
* empty array while the service is disabled.
|
|
263
|
+
*/
|
|
264
|
+
invalidateTag(tag: string): Promise<string[]>;
|
|
265
|
+
/**
|
|
266
|
+
* Invalidates multiple tags by incrementing each of their version counters in a single batched
|
|
267
|
+
* store write. Duplicate tags are bumped once. An empty list is a no-op, as is the entire call
|
|
268
|
+
* while the service is disabled.
|
|
269
|
+
* @param tags - The tags to invalidate.
|
|
270
|
+
* @returns {Promise<string[]>} The `tags` argument as provided (including any duplicates), or
|
|
271
|
+
* an empty array while the service is disabled.
|
|
272
|
+
*/
|
|
273
|
+
invalidateTags(tags: string[]): Promise<string[]>;
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=cache-tags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-tags.d.ts","sourceRoot":"","sources":["../../../../src/caching/src/utils/cache-tags.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Original library: @cacheable/utils (v2.4.2)
|
|
3
|
+
* - Copyright (c) Jared Wray
|
|
4
|
+
*
|
|
5
|
+
* Modified and integrated in "Inray":
|
|
6
|
+
* - Evgeniy Bushin
|
|
7
|
+
*/
|
|
8
|
+
/***/
|
|
9
|
+
/**
|
|
10
|
+
* Prefix applied to every store key written by the service so its metadata cannot collide with
|
|
11
|
+
* user-supplied cache keys.
|
|
12
|
+
*/
|
|
13
|
+
const RESERVED_PREFIX = '--cacheable--tags--';
|
|
14
|
+
/** Namespace used when none is supplied to the constructor. */
|
|
15
|
+
const DEFAULT_NAMESPACE = 'default';
|
|
16
|
+
/**
|
|
17
|
+
* Provides tag-based cache invalidation on top of any {@link Keyv} store. It is store-agnostic and
|
|
18
|
+
* requires no adapter changes.
|
|
19
|
+
*
|
|
20
|
+
* The service uses a lazy invalidation model rather than scanning and deleting keys. Each tag has a
|
|
21
|
+
* monotonically increasing version counter; {@link CacheTags.invalidateTag} simply increments
|
|
22
|
+
* it. When a key is tagged via {@link CacheTags.setKeyTags}, a snapshot of its tags' current
|
|
23
|
+
* versions is stored alongside it. {@link CacheTags.isKeyFresh} compares that snapshot against
|
|
24
|
+
* the live versions — if any tag has been incremented since, the key is considered stale. Stale
|
|
25
|
+
* entries are not deleted explicitly; they are expected to fall out of the cache via their TTL.
|
|
26
|
+
*
|
|
27
|
+
* This keeps invalidation constant-time regardless of how many keys reference a tag, at the cost of
|
|
28
|
+
* one additional `isKeyFresh` read per cache lookup.
|
|
29
|
+
*
|
|
30
|
+
* The service can be disabled via the `enabled` option or property so integrations pay no cost for
|
|
31
|
+
* untagged workloads: while disabled, every method is a no-op — reads return their neutral value
|
|
32
|
+
* and writes are skipped. The service must be explicitly enabled to use tags; it never enables
|
|
33
|
+
* itself, which keeps behavior consistent across distributed instances sharing a store.
|
|
34
|
+
*
|
|
35
|
+
* All metadata is written under a reserved prefix so it cannot collide with user keys:
|
|
36
|
+
* - `--cacheable--tags--:<namespace>:tag:<tag>` → integer version counter (stored without TTL).
|
|
37
|
+
* - `--cacheable--tags--:<namespace>:key:<key>` → the {@link KeyTagEntry} snapshot.
|
|
38
|
+
*
|
|
39
|
+
* Note: the read-version-then-write-snapshot sequence in `setKeyTags` is not atomic across
|
|
40
|
+
* processes. A concurrent `invalidateTag` running between the read and the write can leave a freshly
|
|
41
|
+
* written key referencing a stale version.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const cacheTags = new CacheTags({ store: new Keyv(), namespace: 'app' });
|
|
46
|
+
* await cacheTags.setKeyTags('user:42', ['users', 'org:7'], { ttl: 3600000 });
|
|
47
|
+
* await cacheTags.isKeyFresh('user:42'); // true
|
|
48
|
+
* await cacheTags.invalidateTag('users');
|
|
49
|
+
* await cacheTags.isKeyFresh('user:42'); // false
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export class CacheTags {
|
|
53
|
+
_store;
|
|
54
|
+
_namespace;
|
|
55
|
+
_enabled;
|
|
56
|
+
_onError;
|
|
57
|
+
/**
|
|
58
|
+
* Creates a new tag service.
|
|
59
|
+
* @param {CacheTagsOptions} options - The store, optional namespace, enabled state, and
|
|
60
|
+
* non-blocking error handler to use.
|
|
61
|
+
*/
|
|
62
|
+
constructor(options) {
|
|
63
|
+
this._store = options.store;
|
|
64
|
+
this._namespace = options.namespace ?? DEFAULT_NAMESPACE;
|
|
65
|
+
this._enabled = options.enabled ?? true;
|
|
66
|
+
this._onError = options.onError;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The Keyv store backing this service.
|
|
70
|
+
* @returns {Keyv} The store provided to the constructor.
|
|
71
|
+
*/
|
|
72
|
+
get store() {
|
|
73
|
+
return this._store;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The namespace isolating this service's tags and keys within the store.
|
|
77
|
+
* @returns {string} The configured namespace, or `"default"` if none was provided.
|
|
78
|
+
*/
|
|
79
|
+
get namespace() {
|
|
80
|
+
return this._namespace;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Whether the service is enabled. While disabled, every method is a no-op — read methods
|
|
84
|
+
* return their neutral value and writes are skipped — so integrations pay no extra store
|
|
85
|
+
* reads for untagged workloads. The service must be explicitly enabled to use tags; it never
|
|
86
|
+
* enables itself.
|
|
87
|
+
* @returns {boolean} Whether the service is enabled.
|
|
88
|
+
*/
|
|
89
|
+
get enabled() {
|
|
90
|
+
return this._enabled;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Sets whether the service is enabled.
|
|
94
|
+
* @param {boolean} enabled Whether the service is enabled.
|
|
95
|
+
*/
|
|
96
|
+
set enabled(enabled) {
|
|
97
|
+
this._enabled = enabled;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Builds the reserved store key under which a tag's version counter is stored.
|
|
101
|
+
* @param tag - The tag name.
|
|
102
|
+
* @returns {string} The namespaced store key for the tag's version.
|
|
103
|
+
*/
|
|
104
|
+
tagKey(tag) {
|
|
105
|
+
return `${RESERVED_PREFIX}:${this._namespace}:tag:${tag}`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Builds the reserved store key under which a cache key's tag snapshot is stored.
|
|
109
|
+
* @param key - The cache key being tagged.
|
|
110
|
+
* @returns {string} The namespaced store key for the key's snapshot.
|
|
111
|
+
*/
|
|
112
|
+
keyEntryKey(key) {
|
|
113
|
+
return `${RESERVED_PREFIX}:${this._namespace}:key:${key}`;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Builds the common prefix shared by every key-snapshot entry in this namespace. Used to filter
|
|
117
|
+
* key entries when iterating the store.
|
|
118
|
+
* @returns {string} The namespaced key-entry prefix.
|
|
119
|
+
*/
|
|
120
|
+
keyPrefix() {
|
|
121
|
+
return `${RESERVED_PREFIX}:${this._namespace}:key:`;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Reads the current version of a single tag.
|
|
125
|
+
* @param tag - The tag name.
|
|
126
|
+
* @returns {Promise<number>} The tag's version, or `0` if it has never been invalidated.
|
|
127
|
+
*/
|
|
128
|
+
async getTagVersion(tag) {
|
|
129
|
+
const version = await this._store.get(this.tagKey(tag));
|
|
130
|
+
return typeof version === 'number' ? version : 0;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Reads the current versions of multiple tags in a single batched store read.
|
|
134
|
+
* @param tags - The tag names to look up.
|
|
135
|
+
* @returns {Promise<number[]>} The versions in the same order as `tags`; entries that have never
|
|
136
|
+
* been invalidated resolve to `0`. Returns an empty array when `tags` is empty.
|
|
137
|
+
*/
|
|
138
|
+
async getTagVersions(tags) {
|
|
139
|
+
if (tags.length === 0) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
const tagKeys = tags.map((tag) => this.tagKey(tag));
|
|
143
|
+
const raw = await this._store.get(tagKeys);
|
|
144
|
+
return tags.map((_, i) => {
|
|
145
|
+
const value = raw?.[i];
|
|
146
|
+
return typeof value === 'number' ? value : 0;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Reports a fire-and-forget failure to the `onError` handler, if one was provided.
|
|
151
|
+
* @param error - The error raised by the non-blocking operation.
|
|
152
|
+
*/
|
|
153
|
+
handleNonBlockingError(error) {
|
|
154
|
+
this._onError?.(error);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Reads the version snapshot of each tag and writes the key's tag snapshot to the store.
|
|
158
|
+
* @param key - The cache key to tag.
|
|
159
|
+
* @param tags - The tags to associate with the key.
|
|
160
|
+
* @param ttl - Time-to-live in milliseconds for the snapshot.
|
|
161
|
+
* @returns {Promise<void>} Resolves once the snapshot has been written.
|
|
162
|
+
*/
|
|
163
|
+
async writeKeyTags(key, tags, ttl) {
|
|
164
|
+
const uniqueTags = [...new Set(tags)];
|
|
165
|
+
const versions = await this.getTagVersions(uniqueTags);
|
|
166
|
+
const snapshot = {};
|
|
167
|
+
for (let i = 0; i < uniqueTags.length; i++) {
|
|
168
|
+
snapshot[uniqueTags[i]] = versions[i];
|
|
169
|
+
}
|
|
170
|
+
const entry = { tags: snapshot };
|
|
171
|
+
await this._store.set(this.keyEntryKey(key), entry, ttl);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Associates a cache key with a set of tags by recording a snapshot of each tag's current
|
|
175
|
+
* version. Call this whenever you write a fresh value to the cache. Duplicate tags are ignored.
|
|
176
|
+
* No-op while the service is disabled.
|
|
177
|
+
* @param key - The cache key to tag.
|
|
178
|
+
* @param tags - The tags to associate with the key.
|
|
179
|
+
* @param {SetKeyTagsOptions} [options] - Optional settings, such as a `ttl` for the snapshot or
|
|
180
|
+
* `nonBlocking` to fire-and-forget the write.
|
|
181
|
+
* @returns {Promise<void>} Resolves once the snapshot has been written, or immediately when
|
|
182
|
+
* `nonBlocking` is set.
|
|
183
|
+
*/
|
|
184
|
+
async setKeyTags(key, tags, options) {
|
|
185
|
+
if (!this._enabled) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const work = this.writeKeyTags(key, tags, options?.ttl);
|
|
189
|
+
if (options?.nonBlocking) {
|
|
190
|
+
work.catch((error) => {
|
|
191
|
+
this.handleNonBlockingError(error);
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
await work;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Removes a key's tag snapshot. After this, {@link CacheTags.isKeyFresh} returns `false`
|
|
199
|
+
* for the key. Use when the cached value itself is deleted. No-op while the service is
|
|
200
|
+
* disabled.
|
|
201
|
+
* @param key - The cache key whose snapshot should be removed.
|
|
202
|
+
* @param {RemoveKeysOptions} [options] - Optional settings, such as `nonBlocking` to
|
|
203
|
+
* fire-and-forget the removal.
|
|
204
|
+
* @returns {Promise<void>} Resolves once the snapshot has been deleted, or immediately when
|
|
205
|
+
* `nonBlocking` is set.
|
|
206
|
+
*/
|
|
207
|
+
async removeKey(key, options) {
|
|
208
|
+
await this.removeKeys([key], options);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Removes multiple keys' tag snapshots in a single batched store delete. After this,
|
|
212
|
+
* {@link CacheTags.isKeyFresh} returns `false` for each key. An empty list is a no-op, as is
|
|
213
|
+
* the entire call while the service is disabled.
|
|
214
|
+
* @param keys - The cache keys whose snapshots should be removed.
|
|
215
|
+
* @param {RemoveKeysOptions} [options] - Optional settings, such as `nonBlocking` to
|
|
216
|
+
* fire-and-forget the removal.
|
|
217
|
+
* @returns {Promise<void>} Resolves once the snapshots have been deleted, or immediately when
|
|
218
|
+
* `nonBlocking` is set.
|
|
219
|
+
*/
|
|
220
|
+
async removeKeys(keys, options) {
|
|
221
|
+
if (!this._enabled || keys.length === 0) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const entryKeys = keys.map((key) => this.keyEntryKey(key));
|
|
225
|
+
const work = this._store.deleteMany(entryKeys);
|
|
226
|
+
if (options?.nonBlocking) {
|
|
227
|
+
work.catch((error) => {
|
|
228
|
+
this.handleNonBlockingError(error);
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
await work;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Determines whether a key's cached value can still be trusted. A key is fresh only when a
|
|
236
|
+
* snapshot exists for it and every tag in that snapshot still has the version it had at set time.
|
|
237
|
+
* A key with no tags is trivially fresh. Call this before returning a value from your cache.
|
|
238
|
+
* Always returns `true` while the service is disabled.
|
|
239
|
+
* @param key - The cache key to check.
|
|
240
|
+
* @returns {Promise<boolean>} `true` if the key is still fresh; `false` if it is unknown or any of
|
|
241
|
+
* its tags has been invalidated since the snapshot was taken.
|
|
242
|
+
*/
|
|
243
|
+
async isKeyFresh(key) {
|
|
244
|
+
if (!this._enabled) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
const entry = await this._store.get(this.keyEntryKey(key));
|
|
248
|
+
if (!entry?.tags) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const tags = Object.keys(entry.tags);
|
|
252
|
+
const currentVersions = await this.getTagVersions(tags);
|
|
253
|
+
for (let i = 0; i < tags.length; i++) {
|
|
254
|
+
if (currentVersions[i] !== entry.tags[tags[i]]) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Determines whether a key's cached value is known to be stale due to tag invalidation. This is
|
|
262
|
+
* the complement of {@link CacheTags.isKeyFresh} for tagged keys, but treats keys without a
|
|
263
|
+
* snapshot as not stale — making it safe to call for every cache lookup, including keys that were
|
|
264
|
+
* never tagged. Always returns `false` while the service is disabled.
|
|
265
|
+
* @param key - The cache key to check.
|
|
266
|
+
* @returns {Promise<boolean>} `true` only when a snapshot exists for the key and at least one of
|
|
267
|
+
* its tags has been invalidated since the snapshot was taken; `false` otherwise (including when
|
|
268
|
+
* the key has no snapshot).
|
|
269
|
+
*/
|
|
270
|
+
async isKeyStale(key) {
|
|
271
|
+
if (!this._enabled) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
const staleKeys = await this.getStaleKeys([key]);
|
|
275
|
+
return staleKeys.length > 0;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Determines which of the given keys are known to be stale due to tag invalidation, using two
|
|
279
|
+
* batched store reads regardless of how many keys are checked: one for the snapshots and one for
|
|
280
|
+
* the union of their tag versions. Keys without a snapshot are not considered stale. Returns an
|
|
281
|
+
* empty array while the service is disabled.
|
|
282
|
+
* @param keys - The cache keys to check.
|
|
283
|
+
* @returns {Promise<string[]>} The subset of `keys` whose snapshot references at least one tag
|
|
284
|
+
* that has been invalidated since the snapshot was taken.
|
|
285
|
+
*/
|
|
286
|
+
async getStaleKeys(keys) {
|
|
287
|
+
if (!this._enabled || keys.length === 0) {
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
const entryKeys = keys.map((key) => this.keyEntryKey(key));
|
|
291
|
+
const entries = await this._store.get(entryKeys);
|
|
292
|
+
const tagSet = new Set();
|
|
293
|
+
for (const entry of entries) {
|
|
294
|
+
if (entry?.tags) {
|
|
295
|
+
for (const tag of Object.keys(entry.tags)) {
|
|
296
|
+
tagSet.add(tag);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const tags = [...tagSet];
|
|
301
|
+
const versions = await this.getTagVersions(tags);
|
|
302
|
+
const currentVersions = new Map();
|
|
303
|
+
for (let i = 0; i < tags.length; i++) {
|
|
304
|
+
currentVersions.set(tags[i], versions[i]);
|
|
305
|
+
}
|
|
306
|
+
const staleKeys = [];
|
|
307
|
+
for (const [i, entry] of entries.entries()) {
|
|
308
|
+
if (!entry?.tags) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
for (const [tag, version] of Object.entries(entry.tags)) {
|
|
312
|
+
if (currentVersions.get(tag) !== version) {
|
|
313
|
+
staleKeys.push(keys[i]);
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return staleKeys;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Returns the tags currently associated with a key. Returns `undefined` while the service is
|
|
322
|
+
* disabled.
|
|
323
|
+
* @param key - The cache key to look up.
|
|
324
|
+
* @returns {Promise<string[] | undefined>} The tag names from the key's snapshot, or `undefined`
|
|
325
|
+
* if the key has no snapshot.
|
|
326
|
+
*/
|
|
327
|
+
async getTags(key) {
|
|
328
|
+
if (!this._enabled) {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
const entry = await this._store.get(this.keyEntryKey(key));
|
|
332
|
+
if (!entry?.tags) {
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
return Object.keys(entry.tags);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Returns all cache keys whose snapshot references the given tag. This scans every key entry in
|
|
339
|
+
* the namespace via the Keyv iterator, making it an `O(N)` operation intended for debugging and
|
|
340
|
+
* tests rather than hot paths. Returns an empty array if the underlying store exposes no iterator
|
|
341
|
+
* or while the service is disabled.
|
|
342
|
+
* @param tag - The tag to search for.
|
|
343
|
+
* @returns {Promise<string[]>} The cache keys (with the reserved prefix stripped) referencing the tag.
|
|
344
|
+
*/
|
|
345
|
+
async getKeysByTag(tag) {
|
|
346
|
+
const result = [];
|
|
347
|
+
if (!this._enabled) {
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
const prefix = this.keyPrefix();
|
|
351
|
+
const iterator = this._store.iterator?.(this._store.namespace);
|
|
352
|
+
if (!iterator) {
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
for await (const [storedKey, value] of iterator) {
|
|
356
|
+
if (typeof storedKey !== 'string' || !storedKey.startsWith(prefix)) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const entry = value;
|
|
360
|
+
if (entry?.tags && Object.hasOwn(entry.tags, tag)) {
|
|
361
|
+
result.push(storedKey.slice(prefix.length));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Invalidates a single tag by incrementing its version counter. Every key whose snapshot
|
|
368
|
+
* references this tag becomes stale immediately. Runs in constant time regardless of how many
|
|
369
|
+
* keys reference the tag. No-op while the service is disabled.
|
|
370
|
+
* @param tag - The tag to invalidate.
|
|
371
|
+
* @returns {Promise<string[]>} A single-element array containing the invalidated tag, or an
|
|
372
|
+
* empty array while the service is disabled.
|
|
373
|
+
*/
|
|
374
|
+
async invalidateTag(tag) {
|
|
375
|
+
if (!this._enabled) {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
const current = await this.getTagVersion(tag);
|
|
379
|
+
await this._store.set(this.tagKey(tag), current + 1);
|
|
380
|
+
return [tag];
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Invalidates multiple tags by incrementing each of their version counters in a single batched
|
|
384
|
+
* store write. Duplicate tags are bumped once. An empty list is a no-op, as is the entire call
|
|
385
|
+
* while the service is disabled.
|
|
386
|
+
* @param tags - The tags to invalidate.
|
|
387
|
+
* @returns {Promise<string[]>} The `tags` argument as provided (including any duplicates), or
|
|
388
|
+
* an empty array while the service is disabled.
|
|
389
|
+
*/
|
|
390
|
+
async invalidateTags(tags) {
|
|
391
|
+
if (!this._enabled) {
|
|
392
|
+
return [];
|
|
393
|
+
}
|
|
394
|
+
const uniqueTags = [...new Set(tags)];
|
|
395
|
+
if (uniqueTags.length === 0) {
|
|
396
|
+
return tags;
|
|
397
|
+
}
|
|
398
|
+
const versions = await this.getTagVersions(uniqueTags);
|
|
399
|
+
const kvPairs = [];
|
|
400
|
+
for (let i = 0; i < uniqueTags.length; i++) {
|
|
401
|
+
kvPairs.push({ key: this.tagKey(uniqueTags[i]), value: versions[i] + 1 });
|
|
402
|
+
}
|
|
403
|
+
await this._store.setMany(kvPairs);
|
|
404
|
+
return tags;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
//# sourceMappingURL=cache-tags.js.map
|