@warlock.js/cache 5.16.0 → 5.17.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/CHANGELOG.md +12 -0
- package/cjs/index.cjs +48 -23
- package/cjs/index.cjs.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +6 -1
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +9 -4
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.mjs +1 -1
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-cache-driver.mjs +9 -3
- package/esm/drivers/memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +1 -1
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +3 -3
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +1 -1
- package/esm/drivers/redis-cache-driver.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +15 -2
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +24 -7
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +1 -2
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to `@warlock.js/cache` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.17.0 (Unreleased)
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Cache misses and expiries log at `info` instead of `warn`. A cache miss or expiry is normal behaviour, not a warning.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- `cache.tags([...]).invalidate()` now deletes the tagged entries when a `globalPrefix` is configured, whether static (`"store"`) or a function. Before, the tag index stored each key with the prefix already applied, and invalidation passed that key back through `remove()`, which applied the prefix a second time. So it dropped the tag index but deleted none of the tagged entries, and reads stayed stale until TTL. Every scaffolded app sets a `globalPrefix`. The tag index now stores the un-prefixed key in all of these paths: `tags().set()`, inline `set(key, value, { tags })`, `tags().remove()`, the scoped `cache.namespace(...).tags(...)` handle (including `setNX`), and the `similar()` tag filter. `remove()` applies the prefix exactly once, on every driver. With a function prefix, invalidation uses the prefix that is current when it runs, which is the same prefix the tag index itself is read under. The two therefore agree as long as the prefix is stable for a given app or tenant.
|
|
16
|
+
- `MemoryCacheDriver.similar()` returned no results whenever a `globalPrefix` was set, because it read each stored, already-prefixed key back through `get()`, which prefixed it again. It now reads entries by their stored key.
|
|
17
|
+
- **Upgrade note:** entries tagged before this upgrade are indexed under the old, already-prefixed form, so invalidation still can't reach them. Their tag index is dropped on the first `invalidate()`, which leaves those entries orphaned. They expire by their TTL, or `flush()` clears them right away.
|
|
18
|
+
|
|
7
19
|
## 5.15.0 - 2026-09-18
|
|
8
20
|
|
|
9
21
|
### Fixed
|
package/cjs/index.cjs
CHANGED
|
@@ -627,8 +627,7 @@ var TaggedScopedCache = class {
|
|
|
627
627
|
const allTags = mergeTagSets(this.scope.defaults.tags, this.handleTags);
|
|
628
628
|
if (!allTags || allTags.length === 0) return true;
|
|
629
629
|
const scopedKey = this.buildScopedKey(key);
|
|
630
|
-
|
|
631
|
-
await this.scope.source.tags(allTags).storeTagRelationship(parsedKey);
|
|
630
|
+
await this.scope.source.tags(allTags).storeTagRelationship(scopedKey);
|
|
632
631
|
return true;
|
|
633
632
|
}
|
|
634
633
|
/**
|
|
@@ -1662,20 +1661,36 @@ var TaggedCache = class {
|
|
|
1662
1661
|
* Public alias of the tag-index writer. Called by `BaseCacheDriver.applyTags`
|
|
1663
1662
|
* when tags are passed inline through `CacheSetOptions.tags`.
|
|
1664
1663
|
*
|
|
1664
|
+
* Takes the caller's key, never the driver-parsed one — see {@link indexedKey}.
|
|
1665
|
+
*
|
|
1665
1666
|
* @internal — public for cross-class use within this package; not part of the
|
|
1666
1667
|
* stable consumer API.
|
|
1667
1668
|
*/
|
|
1668
|
-
async storeTagRelationship(
|
|
1669
|
+
async storeTagRelationship(key) {
|
|
1670
|
+
const indexedKey = this.indexedKey(key);
|
|
1669
1671
|
for (const tag of this.cacheTags) {
|
|
1670
1672
|
const tagKey = this.tagKey(tag);
|
|
1671
1673
|
const keys = await this.driver.get(tagKey) || [];
|
|
1672
|
-
if (!keys.includes(
|
|
1673
|
-
keys.push(
|
|
1674
|
+
if (!keys.includes(indexedKey)) {
|
|
1675
|
+
keys.push(indexedKey);
|
|
1674
1676
|
await this.driver.set(tagKey, keys, Infinity);
|
|
1675
1677
|
}
|
|
1676
1678
|
}
|
|
1677
1679
|
}
|
|
1678
1680
|
/**
|
|
1681
|
+
* The form a key takes inside a tag index: normalized but un-prefixed.
|
|
1682
|
+
*
|
|
1683
|
+
* The index is read back and deleted through `driver.remove(key)`, which
|
|
1684
|
+
* applies `globalPrefix` itself — so the index must hold the key *before*
|
|
1685
|
+
* the prefix, or invalidation would prefix it twice and delete nothing.
|
|
1686
|
+
* With a function prefix, the current prefix is re-applied at invalidation
|
|
1687
|
+
* time; the tag index key is resolved under that same prefix, so the two
|
|
1688
|
+
* agree as long as the prefix is stable for a given request/tenant.
|
|
1689
|
+
*/
|
|
1690
|
+
indexedKey(key) {
|
|
1691
|
+
return parseCacheKey(key);
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1679
1694
|
* Get all keys associated with tags
|
|
1680
1695
|
*/
|
|
1681
1696
|
async getTaggedKeys() {
|
|
@@ -1691,9 +1706,8 @@ var TaggedCache = class {
|
|
|
1691
1706
|
* {@inheritdoc}
|
|
1692
1707
|
*/
|
|
1693
1708
|
async set(key, value, ttlOrOptions) {
|
|
1694
|
-
const parsedKey = this.driver.parseKey(key);
|
|
1695
1709
|
await this.driver.set(key, value, ttlOrOptions);
|
|
1696
|
-
await this.storeTaggedKey(
|
|
1710
|
+
await this.storeTaggedKey(key);
|
|
1697
1711
|
return value;
|
|
1698
1712
|
}
|
|
1699
1713
|
/**
|
|
@@ -1706,11 +1720,11 @@ var TaggedCache = class {
|
|
|
1706
1720
|
* {@inheritdoc}
|
|
1707
1721
|
*/
|
|
1708
1722
|
async remove(key) {
|
|
1709
|
-
const
|
|
1723
|
+
const indexedKey = this.indexedKey(key);
|
|
1710
1724
|
await this.driver.remove(key);
|
|
1711
1725
|
for (const tag of this.cacheTags) {
|
|
1712
1726
|
const tagKey = this.tagKey(tag);
|
|
1713
|
-
const updatedKeys = (await this.driver.get(tagKey) || []).filter((k) => k !==
|
|
1727
|
+
const updatedKeys = (await this.driver.get(tagKey) || []).filter((k) => k !== indexedKey);
|
|
1714
1728
|
await this.driver.set(tagKey, updatedKeys, Infinity);
|
|
1715
1729
|
}
|
|
1716
1730
|
}
|
|
@@ -1905,6 +1919,8 @@ var BaseCacheDriver = class {
|
|
|
1905
1919
|
* Used by `similar()` to narrow the candidate pool before similarity ranking.
|
|
1906
1920
|
*
|
|
1907
1921
|
* Returns `null` when no tags are passed (callers should treat that as "no filter").
|
|
1922
|
+
* Keys come back parsed (prefixed) — the index stores them un-prefixed, but
|
|
1923
|
+
* callers match them against their own parsed storage keys.
|
|
1908
1924
|
*/
|
|
1909
1925
|
async getKeysForTags(tags) {
|
|
1910
1926
|
if (!tags || tags.length === 0) return null;
|
|
@@ -1912,17 +1928,20 @@ var BaseCacheDriver = class {
|
|
|
1912
1928
|
for (const tag of tags) {
|
|
1913
1929
|
const tagKey = `cache:tags:${tag}`;
|
|
1914
1930
|
const keys = await this.get(tagKey) || [];
|
|
1915
|
-
for (const k of keys) allKeys.add(k);
|
|
1931
|
+
for (const k of keys) allKeys.add(this.parseKey(k));
|
|
1916
1932
|
}
|
|
1917
1933
|
return allKeys;
|
|
1918
1934
|
}
|
|
1919
1935
|
/**
|
|
1920
1936
|
* Apply tag relationships after a successful write. Called by drivers once
|
|
1921
1937
|
* the value is in storage.
|
|
1938
|
+
*
|
|
1939
|
+
* Pass the caller's key, not the parsed one: the tag index stores keys
|
|
1940
|
+
* un-prefixed so `invalidate()` can delete them through `remove(key)`.
|
|
1922
1941
|
*/
|
|
1923
|
-
async applyTags(
|
|
1942
|
+
async applyTags(key, tags) {
|
|
1924
1943
|
if (tags.length === 0) return;
|
|
1925
|
-
await this.tags(tags).storeTagRelationship(
|
|
1944
|
+
await this.tags(tags).storeTagRelationship(key);
|
|
1926
1945
|
}
|
|
1927
1946
|
/**
|
|
1928
1947
|
* {@inheritdoc}
|
|
@@ -2113,7 +2132,7 @@ var BaseCacheDriver = class {
|
|
|
2113
2132
|
if (!this.shouldLog) return;
|
|
2114
2133
|
if (typeof key === "string") key = key.replace(/\//g, ".");
|
|
2115
2134
|
if (operation === "connectionFailed") _warlock_js_logger.log.fatal(`cache.${this.name}`, operation, key);
|
|
2116
|
-
if (operation == "notFound" || operation == "expired") return _warlock_js_logger.log.
|
|
2135
|
+
if (operation == "notFound" || operation == "expired") return _warlock_js_logger.log.info("cache." + this.name, operation, (key ? key + " " : "") + messages[operation]);
|
|
2117
2136
|
if (operation.endsWith("ed")) return _warlock_js_logger.log.success("cache." + this.name, operation, (key ? key + " " : "") + messages[operation]);
|
|
2118
2137
|
_warlock_js_logger.log.info("cache." + this.name, operation, (key ? key + " " : "") + messages[operation]);
|
|
2119
2138
|
}
|
|
@@ -2422,7 +2441,7 @@ var FileCacheDriver = class extends BaseCacheDriver {
|
|
|
2422
2441
|
const fileDirectory = this.keyDirectory(parsedKey);
|
|
2423
2442
|
await (0, _warlock_js_fs.ensureDirectoryAsync)(fileDirectory);
|
|
2424
2443
|
await (0, _warlock_js_fs.putJsonFileAsync)(path.default.resolve(fileDirectory, this.fileName), data);
|
|
2425
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
2444
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
2426
2445
|
this.log("cached", parsedKey);
|
|
2427
2446
|
await this.emit("set", {
|
|
2428
2447
|
key: parsedKey,
|
|
@@ -2652,7 +2671,7 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
2652
2671
|
this.addNode(newNode);
|
|
2653
2672
|
if (this.cache.size > this.capacity) this.removeTail();
|
|
2654
2673
|
}
|
|
2655
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
2674
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
2656
2675
|
this.log("cached", parsedKey);
|
|
2657
2676
|
await this.emit("set", {
|
|
2658
2677
|
key: parsedKey,
|
|
@@ -2906,7 +2925,7 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
2906
2925
|
(0, _mongez_reinforcements.set)(this.data, parsedKey, data);
|
|
2907
2926
|
this.trackAccess(parsedKey);
|
|
2908
2927
|
if (!exists && this.options.maxSize) await this.enforceMaxSize();
|
|
2909
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
2928
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
2910
2929
|
if (vector) this.vectorIndex.set(parsedKey, vector.slice());
|
|
2911
2930
|
this.log("cached", parsedKey);
|
|
2912
2931
|
await this.emit("set", {
|
|
@@ -3047,8 +3066,14 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
3047
3066
|
const hits = [];
|
|
3048
3067
|
for (const [parsedKey, stored] of this.vectorIndex) {
|
|
3049
3068
|
if (tagFilter && !tagFilter.has(parsedKey)) continue;
|
|
3050
|
-
const
|
|
3051
|
-
if (
|
|
3069
|
+
const entry = (0, _mongez_reinforcements.get)(this.data, parsedKey);
|
|
3070
|
+
if (!entry) continue;
|
|
3071
|
+
if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) continue;
|
|
3072
|
+
let value = entry.data;
|
|
3073
|
+
if (value !== null && value !== void 0) {
|
|
3074
|
+
const type = typeof value;
|
|
3075
|
+
if (type !== "string" && type !== "number" && type !== "boolean") value = structuredClone(value);
|
|
3076
|
+
}
|
|
3052
3077
|
const score = cosineSimilarity(vector, stored);
|
|
3053
3078
|
if (options.threshold !== void 0 && score < options.threshold) continue;
|
|
3054
3079
|
hits.push({
|
|
@@ -3200,7 +3225,7 @@ var MockCacheDriver = class extends BaseCacheDriver {
|
|
|
3200
3225
|
};
|
|
3201
3226
|
const data = this.prepareDataForStorage(value, ttl, staleAt);
|
|
3202
3227
|
this.storage.set(parsedKey, data);
|
|
3203
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
3228
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
3204
3229
|
this.log("cached", parsedKey);
|
|
3205
3230
|
await this.emit("set", {
|
|
3206
3231
|
key: parsedKey,
|
|
@@ -3625,7 +3650,7 @@ var PgCacheDriver = class extends BaseCacheDriver {
|
|
|
3625
3650
|
wasSet: false,
|
|
3626
3651
|
existing: await this.get(key)
|
|
3627
3652
|
};
|
|
3628
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
3653
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
3629
3654
|
this.log("cached", parsedKey);
|
|
3630
3655
|
await this.emit("set", {
|
|
3631
3656
|
key: parsedKey,
|
|
@@ -3646,7 +3671,7 @@ var PgCacheDriver = class extends BaseCacheDriver {
|
|
|
3646
3671
|
wasSet: false,
|
|
3647
3672
|
existing: null
|
|
3648
3673
|
};
|
|
3649
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
3674
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
3650
3675
|
this.log("cached", parsedKey);
|
|
3651
3676
|
await this.emit("set", {
|
|
3652
3677
|
key: parsedKey,
|
|
@@ -3662,7 +3687,7 @@ var PgCacheDriver = class extends BaseCacheDriver {
|
|
|
3662
3687
|
VALUES (${valList})
|
|
3663
3688
|
ON CONFLICT (key) DO UPDATE
|
|
3664
3689
|
SET ${setClause}`, params);
|
|
3665
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
3690
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
3666
3691
|
this.log("cached", parsedKey);
|
|
3667
3692
|
await this.emit("set", {
|
|
3668
3693
|
key: parsedKey,
|
|
@@ -3932,7 +3957,7 @@ var RedisCacheDriver = class extends BaseCacheDriver {
|
|
|
3932
3957
|
wasSet: false,
|
|
3933
3958
|
existing: onConflict === "create" ? await this.get(key) : null
|
|
3934
3959
|
};
|
|
3935
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
3960
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
3936
3961
|
if (staleAt !== void 0) {
|
|
3937
3962
|
const sidecarOptions = {};
|
|
3938
3963
|
if (hasExpiry) sidecarOptions.EX = ttl;
|