@warlock.js/cache 5.30.0 → 5.32.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 +26 -8
- package/README.md +12 -21
- package/cjs/index.cjs +50 -31
- package/cjs/index.cjs.map +1 -1
- package/esm/cache-manager.d.mts +5 -5
- package/esm/cache-manager.d.mts.map +1 -1
- package/esm/cache-manager.mjs +2 -2
- package/esm/cache-manager.mjs.map +1 -1
- package/esm/cached/auto-key.d.mts +1 -2
- package/esm/cached/auto-key.d.mts.map +1 -1
- package/esm/cached/cached.d.mts +4 -6
- package/esm/cached/cached.d.mts.map +1 -1
- package/esm/cached/index.d.mts +2 -1
- package/esm/cached/index.mjs +1 -1
- package/esm/cached/normalize-args.d.mts +3 -5
- package/esm/cached/normalize-args.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +5 -5
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +4 -3
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.d.mts +1 -3
- package/esm/drivers/file-cache-driver.d.mts.map +1 -1
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/in-memory-tag-index.d.mts +1 -2
- package/esm/drivers/in-memory-tag-index.d.mts.map +1 -1
- package/esm/drivers/index.d.mts +2 -1
- package/esm/drivers/index.mjs +1 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts +5 -4
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +6 -4
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts +4 -4
- 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/memory-extended-cache-driver.d.mts +1 -3
- package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.d.mts +4 -4
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +11 -4
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/null-cache-driver.d.mts +1 -3
- package/esm/drivers/null-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.d.mts +4 -4
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +6 -3
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.d.mts +9 -9
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +4 -3
- package/esm/drivers/redis-cache-driver.mjs.map +1 -1
- package/esm/index.d.mts +3 -0
- package/esm/list/index.d.mts +2 -1
- package/esm/list/memory-cache-list.d.mts +1 -3
- package/esm/list/memory-cache-list.d.mts.map +1 -1
- package/esm/list/memory-cache-list.mjs.map +1 -1
- package/esm/metrics.d.mts +4 -4
- package/esm/metrics.d.mts.map +1 -1
- package/esm/metrics.mjs +2 -2
- package/esm/metrics.mjs.map +1 -1
- package/esm/scoped-cache.d.mts +4 -4
- package/esm/scoped-cache.d.mts.map +1 -1
- package/esm/scoped-cache.mjs +2 -2
- package/esm/scoped-cache.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +4 -4
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +3 -3
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts +4 -4
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +2 -2
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/esm/types.d.mts +86 -62
- package/esm/types.d.mts.map +1 -1
- package/esm/types.mjs.map +1 -1
- package/esm/utils.d.mts +12 -14
- package/esm/utils.d.mts.map +1 -1
- package/esm/utils.mjs.map +1 -1
- package/llms-full.txt +229 -224
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/skills/apply-cache-patterns/SKILL.md +3 -2
- package/skills/cache-basics/SKILL.md +24 -24
- package/skills/configure-pg-cache/SKILL.md +2 -2
- package/skills/configure-set-options/SKILL.md +14 -14
- package/skills/handle-cache-errors/SKILL.md +8 -8
- package/skills/index.md +4 -1
- package/skills/observe-cache/SKILL.md +8 -8
- package/skills/pick-cache-driver/SKILL.md +24 -23
- package/skills/test-cache-code/SKILL.md +15 -10
- package/skills/use-cache-atomic/SKILL.md +17 -11
- package/skills/use-cache-bulk/SKILL.md +8 -5
- package/skills/use-cache-list/SKILL.md +13 -13
- package/skills/use-cache-lock/SKILL.md +11 -17
- package/skills/use-cache-namespace/SKILL.md +11 -13
- package/skills/use-cache-similarity/SKILL.md +9 -9
- package/skills/use-cache-tags/SKILL.md +13 -13
- package/skills/use-cache-update-merge/SKILL.md +10 -10
- package/skills/use-cache-utils/SKILL.md +17 -17
- package/skills/use-cached-hof/SKILL.md +8 -7
- package/skills/use-swr/SKILL.md +14 -18
package/CHANGELOG.md
CHANGED
|
@@ -4,11 +4,27 @@ 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.32.0 - 2026-10-18
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Pin the package development compiler to TypeScript 7.0.2 and use explicit Node types and source roots with supported module resolution.
|
|
12
|
+
|
|
13
|
+
## 5.31.0 - 2026-10-18
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Real Redis integration coverage for atomic counters and their expiry behavior, including concurrent requests, preserved deadlines and counters recreated after a seeded key expires. Local runs opt in with `WARLOCK_REDIS_INTEGRATION=1`; CI requires a live Redis service. Tests use UUID prefixes and clean only their own keys.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `cache.increment(key, amount, { ttl })` supplies a TTL when an atomic increment creates or recreates a counter, through direct, tagged and scoped calls. Existing expiry deadlines are preserved; an explicit TTL also repairs a live permanent counter across drivers. Redis applies the count and TTL together, so an expired seed cannot leave a permanent counter even when the driver has no default TTL.
|
|
22
|
+
|
|
7
23
|
## 5.30.0 - 2026-10-16
|
|
8
24
|
|
|
9
25
|
### Changed
|
|
10
26
|
|
|
11
|
-
- Email addresses inside logged cache keys are masked (`user.member@example.com` logs as `u***@example.com`), in the base driver's operation log, in error lines (written even with logging off: SWR refresh failures, lock release, clone failures), in the "Cannot increment non-numeric value" error, and in the null driver
|
|
27
|
+
- Email addresses inside logged cache keys are masked (`user.member@example.com` logs as `u***@example.com`), in the base driver's operation log, in error lines (written even with logging off: SWR refresh failures, lock release, clone failures), in the "Cannot increment non-numeric value" error, and in the null driver.
|
|
12
28
|
|
|
13
29
|
### Fixed
|
|
14
30
|
|
|
@@ -87,6 +103,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
87
103
|
### Changed
|
|
88
104
|
|
|
89
105
|
- Lockstep release maintenance and dependency refresh.
|
|
106
|
+
|
|
90
107
|
## 5.20.0 - 2026-09-24
|
|
91
108
|
|
|
92
109
|
### Breaking
|
|
@@ -130,14 +147,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
130
147
|
|
|
131
148
|
- **Atomic `increment`/`decrement`, `pull` and `update`**, with a per-driver guarantee:
|
|
132
149
|
|
|
133
|
-
| Driver
|
|
134
|
-
|
|
|
135
|
-
| `redis`
|
|
136
|
-
| `pg`
|
|
137
|
-
| `memory` / `memoryExtended` / `lru` / `mock` | in-process, synchronous
|
|
138
|
-
| `file`
|
|
150
|
+
| Driver | `increment` / `decrement` | `pull` | `update` |
|
|
151
|
+
| -------------------------------------------- | --------------------------------------------- | ------------------------------------- | -------------------------------------------------------------- |
|
|
152
|
+
| `redis` | cross-server (`INCRBY`, keeps TTL) | cross-server (`GETDEL`, Lua fallback) | cross-server (Lua compare-and-set, bounded retries, `KEEPTTL`) |
|
|
153
|
+
| `pg` | cross-server (one upsert, keeps `expires_at`) | cross-server (`DELETE … RETURNING`) | cross-server (compare-and-set, bounded retries) |
|
|
154
|
+
| `memory` / `memoryExtended` / `lru` / `mock` | in-process, synchronous | in-process, synchronous | serialized per key, in-process only |
|
|
155
|
+
| `file` | serialized per key, in-process only | serialized per key, in-process only | not supported (throws) |
|
|
139
156
|
|
|
140
157
|
`update()` callbacks on Redis and Postgres may run more than once under contention, so keep them side-effect free. To coordinate more than one server, use `redis` or `pg`; `memory` is per-process.
|
|
158
|
+
|
|
141
159
|
- **Tag index primitives** on the driver contract: `tagAdd`, `tagMembers`, `tagRemove`. Redis uses a native SET inside the prefix (a legacy JSON index is upgraded on first touch), Postgres a one-statement JSON-array merge on the index row, and the in-memory drivers a separate store that eviction and expiry never touch. `file` and `null` keep the serialized fallback. `invalidate()` deletes exactly the members it read, writes prune up to 20 dead members, `remove()`/`pull()` detach the key, and tagged `increment`/`pull` use the atomic ops.
|
|
142
160
|
- **`PgCacheDriver.prune(limit = 1000)`** deletes expired rows in batches and returns the count. `set()` also runs it in the background on about 1 in 200 writes, so unique-key workloads no longer grow the table forever.
|
|
143
161
|
- **`lock()` successor-safe release** (see Fixed). There is no renewal, so the TTL must exceed the worst-case duration of the locked work.
|
|
@@ -242,7 +260,7 @@ changelogs.
|
|
|
242
260
|
- Removed the raw `console.log(value)` dump of the full cached payload when `structuredClone` fails in `parseCachedData` — cached values (potentially PII/tokens) no longer leak to stdout; the structured error log with the value's type is kept.
|
|
243
261
|
- **Credential leak via error logging (Medium):** `logError()` and the Redis driver's `connect()` failure path printed the raw `Error` object straight to stdout (`console.log`) or to `log.fatal`, which could include the connection URL — and password — that some Redis/Node client errors echo back in `error.message`/`cause` on connection failure. Both call sites now go through a new `safeErrorInfo()` helper that logs only a redacted `{ message, code }` shape (never the raw error object), with any `scheme://user:pass@` credentials in the message masked to `scheme://[REDACTED]@`. The bare `console.log(error)`/`console.log("Err", error)` calls are gone entirely.
|
|
244
262
|
- **Redis `removeNamespace` blocking `KEYS` scan (Medium):** replaced the blocking `KEYS` command with a non-blocking `SCAN` cursor loop (`client.scanIterator`), so clearing a namespace on a large keyspace no longer stalls the single-threaded Redis event loop for every other tenant/consumer. The existing glob-escaping fix (above) is unchanged.
|
|
245
|
-
- **File driver `removeNamespace` dotted-key gap (Medium):** dotted keys (`ns.a`) are stored as
|
|
263
|
+
- **File driver `removeNamespace` dotted-key gap (Medium):** dotted keys (`ns.a`) are stored as _sibling_ directories under the cache root (see the path-traversal fix above), so removing namespace `ns` — which only ever deleted a directory literally named `ns` — silently left every `ns.*` key on disk. `removeNamespace` now lists the cache root's immediate children, decodes each back to its logical key, and removes every directory whose logical key equals the namespace or starts with `<namespace>.`, matching the boundary semantics the `pg` driver already used for the same contract. Honors `globalPrefix` (previously ignored, so a global flush could wipe the whole cache root instead of scoping to the tenant) and preserves the existing path-containment guard.
|
|
246
264
|
|
|
247
265
|
### Dependencies
|
|
248
266
|
|
package/README.md
CHANGED
|
@@ -33,11 +33,7 @@ Or
|
|
|
33
33
|
First step, we need to define the cache configurations, in your configuration directory create a file to define the cache configurations.
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
import {
|
|
37
|
-
cache,
|
|
38
|
-
type CacheConfigurations,
|
|
39
|
-
MemoryCacheDriver,
|
|
40
|
-
} from "@mongez/warlock.cache";
|
|
36
|
+
import { cache, type CacheConfigurations, MemoryCacheDriver } from "@mongez/warlock.cache";
|
|
41
37
|
|
|
42
38
|
const configurations: CacheConfigurations = {
|
|
43
39
|
drivers: {
|
|
@@ -102,11 +98,11 @@ await cache.set("report", report, "7d");
|
|
|
102
98
|
|
|
103
99
|
// 3. Rich options object
|
|
104
100
|
await cache.set("user:1", user, {
|
|
105
|
-
ttl: "1h",
|
|
106
|
-
expiresAt: new Date("..."),
|
|
107
|
-
tags: ["users", "active"],
|
|
108
|
-
onConflict: "create",
|
|
109
|
-
driver: "redis",
|
|
101
|
+
ttl: "1h", // or number of seconds — mutually exclusive with expiresAt
|
|
102
|
+
expiresAt: new Date("..."), // absolute deadline (Date or epoch ms)
|
|
103
|
+
tags: ["users", "active"], // inline tags
|
|
104
|
+
onConflict: "create", // "create" | "update" | "upsert" (default)
|
|
105
|
+
driver: "redis", // route this single call to a non-default driver
|
|
110
106
|
});
|
|
111
107
|
```
|
|
112
108
|
|
|
@@ -117,8 +113,8 @@ cache.setCacheConfigurations({
|
|
|
117
113
|
default: "redis",
|
|
118
114
|
drivers: { redis: RedisCacheDriver, memory: MemoryCacheDriver },
|
|
119
115
|
options: {
|
|
120
|
-
redis: { url: "...", ttl: "7d" },
|
|
121
|
-
memory: { ttl: 3600 },
|
|
116
|
+
redis: { url: "...", ttl: "7d" }, // string OK
|
|
117
|
+
memory: { ttl: 3600 }, // number OK
|
|
122
118
|
},
|
|
123
119
|
});
|
|
124
120
|
```
|
|
@@ -142,7 +138,7 @@ Redis users who know the shorthand: `"create"` maps to `NX`, `"update"` maps to
|
|
|
142
138
|
```ts
|
|
143
139
|
// Read → transform → write under a per-key in-process lock.
|
|
144
140
|
await cache.update<User>("user:1", (current) => {
|
|
145
|
-
if (!current) return null;
|
|
141
|
+
if (!current) return null; // returning null removes the entry
|
|
146
142
|
return { ...current, lastSeen: Date.now() };
|
|
147
143
|
});
|
|
148
144
|
|
|
@@ -166,7 +162,7 @@ const head = await recent.shift();
|
|
|
166
162
|
const firstTen = await recent.slice(0, 10);
|
|
167
163
|
const count = await recent.length();
|
|
168
164
|
|
|
169
|
-
await recent.trim(0, 99);
|
|
165
|
+
await recent.trim(0, 99); // keep only indices 0–99
|
|
170
166
|
await recent.clear();
|
|
171
167
|
```
|
|
172
168
|
|
|
@@ -231,11 +227,7 @@ It could be useful when working with multi tenant applications, we can use the t
|
|
|
231
227
|
Every driver's options have by default `globalPrefix` async function, this function is called each time an operation is performed, and it returns a string, this string is used as a prefix for the key, let's add it to our memory driver.
|
|
232
228
|
|
|
233
229
|
```ts
|
|
234
|
-
import {
|
|
235
|
-
cache,
|
|
236
|
-
type CacheConfigurations,
|
|
237
|
-
MemoryCacheDriver,
|
|
238
|
-
} from "@mongez/warlock.cache";
|
|
230
|
+
import { cache, type CacheConfigurations, MemoryCacheDriver } from "@mongez/warlock.cache";
|
|
239
231
|
|
|
240
232
|
const configurations: CacheConfigurations = {
|
|
241
233
|
drivers: {
|
|
@@ -416,10 +408,9 @@ async function main() {
|
|
|
416
408
|
}
|
|
417
409
|
```
|
|
418
410
|
|
|
419
|
-
|
|
420
411
|
### LRU Cache Driver
|
|
421
412
|
|
|
422
|
-
[LRU Cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)) is a cache driver that caches the data in the memory with LRU algorithm.
|
|
413
|
+
[LRU Cache](<https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)>) is a cache driver that caches the data in the memory with LRU algorithm.
|
|
423
414
|
|
|
424
415
|
So how this works? The LRU cache driver will store the data in the memory, and when the memory is full, it will remove the least recently used item.
|
|
425
416
|
|
package/cjs/index.cjs
CHANGED
|
@@ -20,7 +20,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
20
20
|
}
|
|
21
21
|
return to;
|
|
22
22
|
};
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
24
24
|
value: mod,
|
|
25
25
|
enumerable: true
|
|
26
26
|
}) : target, mod));
|
|
@@ -106,7 +106,6 @@ var CacheMetricsCollector = class {
|
|
|
106
106
|
case "error":
|
|
107
107
|
aggregate.errors += 1;
|
|
108
108
|
driverBucket.errors += 1;
|
|
109
|
-
break;
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
111
|
/**
|
|
@@ -186,7 +185,8 @@ var CacheMetricsCollector = class {
|
|
|
186
185
|
};
|
|
187
186
|
const sorted = [...samples].sort((a, b) => a - b);
|
|
188
187
|
const pick = (quantile) => {
|
|
189
|
-
|
|
188
|
+
const index = Math.min(sorted.length - 1, Math.floor(quantile * sorted.length));
|
|
189
|
+
return sorted[index] ?? 0;
|
|
190
190
|
};
|
|
191
191
|
return {
|
|
192
192
|
p50: pick(.5),
|
|
@@ -666,8 +666,8 @@ var TaggedScopedCache = class {
|
|
|
666
666
|
* Atomic counter increment on the scoped key. Tags aren't applied to
|
|
667
667
|
* subsequent increments — they're attached at first-write time.
|
|
668
668
|
*/
|
|
669
|
-
increment(key, value) {
|
|
670
|
-
return this.scope.increment(key, value);
|
|
669
|
+
increment(key, value, options) {
|
|
670
|
+
return this.scope.increment(key, value, options);
|
|
671
671
|
}
|
|
672
672
|
/**
|
|
673
673
|
* Atomic counter decrement on the scoped key. See {@link increment}.
|
|
@@ -860,8 +860,8 @@ var ScopedCache = class ScopedCache {
|
|
|
860
860
|
* Atomic counter increment on the scoped key. TTL is preserved by the
|
|
861
861
|
* underlying driver — scope ttl is only applied on first write via `set`.
|
|
862
862
|
*/
|
|
863
|
-
increment(key, value) {
|
|
864
|
-
return this.source.increment(this.scopedKey(key), value);
|
|
863
|
+
increment(key, value, options) {
|
|
864
|
+
return this.source.increment(this.scopedKey(key), value, { ttl: options?.ttl ?? this.defaults.ttl });
|
|
865
865
|
}
|
|
866
866
|
/**
|
|
867
867
|
* Atomic counter decrement on the scoped key. See {@link increment} for
|
|
@@ -1321,9 +1321,9 @@ var CacheManager = class {
|
|
|
1321
1321
|
/**
|
|
1322
1322
|
* {@inheritdoc}
|
|
1323
1323
|
*/
|
|
1324
|
-
async increment(key, value) {
|
|
1324
|
+
async increment(key, value, options) {
|
|
1325
1325
|
this.ensureDriverInitialized();
|
|
1326
|
-
return this.currentDriver.increment(key, value);
|
|
1326
|
+
return this.currentDriver.increment(key, value, options);
|
|
1327
1327
|
}
|
|
1328
1328
|
/**
|
|
1329
1329
|
* {@inheritdoc}
|
|
@@ -1772,7 +1772,7 @@ var TaggedCache = class TaggedCache {
|
|
|
1772
1772
|
async addToTag(tagKey, members) {
|
|
1773
1773
|
if (typeof this.driver.tagAdd === "function") return this.driver.tagAdd(tagKey, members);
|
|
1774
1774
|
const current = await this.legacyMembers(tagKey);
|
|
1775
|
-
const merged = [
|
|
1775
|
+
const merged = [.../* @__PURE__ */ new Set([...current, ...members])];
|
|
1776
1776
|
if (merged.length !== current.length) await this.driver.set(tagKey, merged, Infinity);
|
|
1777
1777
|
}
|
|
1778
1778
|
async membersOf(tagKey) {
|
|
@@ -1902,8 +1902,8 @@ var TaggedCache = class TaggedCache {
|
|
|
1902
1902
|
/**
|
|
1903
1903
|
* {@inheritdoc}
|
|
1904
1904
|
*/
|
|
1905
|
-
async increment(key, value = 1) {
|
|
1906
|
-
const newValue = await this.driver.increment(key, value);
|
|
1905
|
+
async increment(key, value = 1, options) {
|
|
1906
|
+
const newValue = await this.driver.increment(key, value, options);
|
|
1907
1907
|
await this.storeTaggedKey(key);
|
|
1908
1908
|
return newValue;
|
|
1909
1909
|
}
|
|
@@ -2319,7 +2319,8 @@ var BaseCacheDriver = class {
|
|
|
2319
2319
|
* The remaining TTL is rounded down, so repeated increments can never push
|
|
2320
2320
|
* a window's deadline later.
|
|
2321
2321
|
*/
|
|
2322
|
-
async increment(key, value = 1) {
|
|
2322
|
+
async increment(key, value = 1, options = {}) {
|
|
2323
|
+
const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
|
|
2323
2324
|
const parsedKey = this.parseKey(key);
|
|
2324
2325
|
return this.runSerialized(parsedKey, async () => {
|
|
2325
2326
|
const entry = await this.getEntry(key);
|
|
@@ -2330,8 +2331,8 @@ var BaseCacheDriver = class {
|
|
|
2330
2331
|
const base = current === null || current === void 0 ? 0 : current;
|
|
2331
2332
|
if (typeof base !== "number") throw new Error(`Cannot increment non-numeric value for key: ${(0, _warlock_js_logger.maskEmailsInText)(parsedKey)}`);
|
|
2332
2333
|
const newValue = base + value;
|
|
2333
|
-
if (!live) await this.set(key, newValue);
|
|
2334
|
-
else if (expiresAt == null || expiresAt === Infinity) await this.set(key, newValue, { ttl: Infinity });
|
|
2334
|
+
if (!live) await this.set(key, newValue, { ttl });
|
|
2335
|
+
else if (expiresAt == null || expiresAt === Infinity) await this.set(key, newValue, { ttl: options.ttl === void 0 ? Infinity : ttl });
|
|
2335
2336
|
else await this.set(key, newValue, { ttl: Math.max(1, Math.floor((expiresAt - now) / 1e3)) });
|
|
2336
2337
|
return newValue;
|
|
2337
2338
|
});
|
|
@@ -3132,7 +3133,8 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
3132
3133
|
* concurrent increments in this process never lose an update. An existing
|
|
3133
3134
|
* node keeps its expiry; a missing key starts from 0 with the default TTL.
|
|
3134
3135
|
*/
|
|
3135
|
-
async increment(key, value = 1) {
|
|
3136
|
+
async increment(key, value = 1, options = {}) {
|
|
3137
|
+
const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
|
|
3136
3138
|
const parsedKey = this.parseKey(key);
|
|
3137
3139
|
const node = this.liveNode(parsedKey);
|
|
3138
3140
|
const current = node ? node.value : 0;
|
|
@@ -3140,9 +3142,10 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
3140
3142
|
const newValue = current + value;
|
|
3141
3143
|
if (node) {
|
|
3142
3144
|
node.value = newValue;
|
|
3145
|
+
if (options.ttl !== void 0 && !Number.isFinite(node.expiresAt) && Number.isFinite(ttl) && ttl > 0) node.expiresAt = Date.now() + ttl * 1e3;
|
|
3143
3146
|
this.moveHead(node);
|
|
3144
3147
|
} else {
|
|
3145
|
-
const newNode = new CacheNode(parsedKey, newValue,
|
|
3148
|
+
const newNode = new CacheNode(parsedKey, newValue, ttl);
|
|
3146
3149
|
this.cache.set(parsedKey, newNode);
|
|
3147
3150
|
this.addNode(newNode);
|
|
3148
3151
|
if (this.cache.size > this.capacity) this.removeTail();
|
|
@@ -3150,7 +3153,7 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
3150
3153
|
await this.emit("set", {
|
|
3151
3154
|
key: parsedKey,
|
|
3152
3155
|
value: newValue,
|
|
3153
|
-
ttl
|
|
3156
|
+
ttl
|
|
3154
3157
|
});
|
|
3155
3158
|
return newValue;
|
|
3156
3159
|
}
|
|
@@ -3503,7 +3506,8 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
3503
3506
|
* concurrent increments in this process never lose an update. An existing
|
|
3504
3507
|
* entry keeps its expiry; a missing key starts from 0 with the default TTL.
|
|
3505
3508
|
*/
|
|
3506
|
-
async increment(key, value = 1) {
|
|
3509
|
+
async increment(key, value = 1, options = {}) {
|
|
3510
|
+
const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
|
|
3507
3511
|
const parsedKey = this.parseKey(key);
|
|
3508
3512
|
const entry = this.readLive(parsedKey);
|
|
3509
3513
|
const current = entry ? entry.data : 0;
|
|
@@ -3511,9 +3515,14 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
3511
3515
|
const newValue = current + value;
|
|
3512
3516
|
if (entry) {
|
|
3513
3517
|
entry.data = newValue;
|
|
3518
|
+
if (options.ttl !== void 0 && !Number.isFinite(entry.expiresAt) && Number.isFinite(ttl) && ttl > 0) {
|
|
3519
|
+
entry.ttl = ttl;
|
|
3520
|
+
entry.expiresAt = Date.now() + ttl * 1e3;
|
|
3521
|
+
this.expiry.set(parsedKey, entry.expiresAt);
|
|
3522
|
+
}
|
|
3514
3523
|
this.touch(parsedKey, entry);
|
|
3515
3524
|
} else {
|
|
3516
|
-
const data = this.prepareDataForStorage(newValue,
|
|
3525
|
+
const data = this.prepareDataForStorage(newValue, ttl);
|
|
3517
3526
|
this.entries.set(parsedKey, data);
|
|
3518
3527
|
if (data.expiresAt !== void 0 && Number.isFinite(data.expiresAt)) this.expiry.set(parsedKey, data.expiresAt);
|
|
3519
3528
|
this.enforceMaxSize(parsedKey);
|
|
@@ -3521,7 +3530,7 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
3521
3530
|
await this.emit("set", {
|
|
3522
3531
|
key: parsedKey,
|
|
3523
3532
|
value: newValue,
|
|
3524
|
-
ttl: entry?.ttl ??
|
|
3533
|
+
ttl: entry?.ttl ?? ttl
|
|
3525
3534
|
});
|
|
3526
3535
|
return newValue;
|
|
3527
3536
|
}
|
|
@@ -3873,19 +3882,25 @@ var MockCacheDriver = class extends BaseCacheDriver {
|
|
|
3873
3882
|
* Synchronous read-add-write (atomic within the process). Keeps an existing
|
|
3874
3883
|
* entry's expiry; a missing key starts from 0 with the default TTL.
|
|
3875
3884
|
*/
|
|
3876
|
-
async increment(key, value = 1) {
|
|
3885
|
+
async increment(key, value = 1, options = {}) {
|
|
3886
|
+
const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
|
|
3877
3887
|
const parsedKey = this.parseKey(key);
|
|
3878
3888
|
this.recordCall("increment", parsedKey, [value]);
|
|
3879
3889
|
const entry = this.liveEntry(parsedKey);
|
|
3880
3890
|
const current = entry ? entry.data : 0;
|
|
3881
3891
|
if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${(0, _warlock_js_logger.maskEmailsInText)(parsedKey)}`);
|
|
3882
3892
|
const newValue = current + value;
|
|
3883
|
-
if (entry)
|
|
3884
|
-
|
|
3893
|
+
if (entry) {
|
|
3894
|
+
entry.data = newValue;
|
|
3895
|
+
if (options.ttl !== void 0 && !Number.isFinite(entry.expiresAt) && Number.isFinite(ttl) && ttl > 0) {
|
|
3896
|
+
entry.ttl = ttl;
|
|
3897
|
+
entry.expiresAt = Date.now() + ttl * 1e3;
|
|
3898
|
+
}
|
|
3899
|
+
} else this.storage.set(parsedKey, this.prepareDataForStorage(newValue, ttl));
|
|
3885
3900
|
await this.emit("set", {
|
|
3886
3901
|
key: parsedKey,
|
|
3887
3902
|
value: newValue,
|
|
3888
|
-
ttl
|
|
3903
|
+
ttl
|
|
3889
3904
|
});
|
|
3890
3905
|
return newValue;
|
|
3891
3906
|
}
|
|
@@ -4562,7 +4577,7 @@ var PgCacheDriver = class extends BaseCacheDriver {
|
|
|
4562
4577
|
* keeps its `expires_at`; a missing or expired row restarts from `value`
|
|
4563
4578
|
* with the driver default TTL. A non-numeric live value is rejected.
|
|
4564
4579
|
*/
|
|
4565
|
-
async increment(key, value = 1) {
|
|
4580
|
+
async increment(key, value = 1, options = {}) {
|
|
4566
4581
|
const parsedKey = this.parseKey(key);
|
|
4567
4582
|
const t = this.table;
|
|
4568
4583
|
const expired = `${t}.expires_at IS NOT NULL AND ${t}.expires_at <= now()`;
|
|
@@ -4571,13 +4586,16 @@ var PgCacheDriver = class extends BaseCacheDriver {
|
|
|
4571
4586
|
ON CONFLICT (key) DO UPDATE
|
|
4572
4587
|
SET value = CASE WHEN ${expired} THEN EXCLUDED.value
|
|
4573
4588
|
ELSE to_jsonb((${t}.value #>> '{}')::numeric + $2::numeric) END,
|
|
4574
|
-
expires_at = CASE WHEN ${expired} THEN EXCLUDED.expires_at
|
|
4589
|
+
expires_at = CASE WHEN ${expired} THEN EXCLUDED.expires_at
|
|
4590
|
+
WHEN $4::boolean AND ${t}.expires_at IS NULL THEN EXCLUDED.expires_at
|
|
4591
|
+
ELSE ${t}.expires_at END,
|
|
4575
4592
|
stale_at = CASE WHEN ${expired} THEN NULL ELSE ${t}.stale_at END
|
|
4576
4593
|
WHERE (${expired}) OR jsonb_typeof(${t}.value) = 'number'
|
|
4577
4594
|
RETURNING value`, [
|
|
4578
4595
|
parsedKey,
|
|
4579
4596
|
value,
|
|
4580
|
-
this.ttlToSeconds(this.ttl)
|
|
4597
|
+
this.ttlToSeconds(options.ttl === void 0 ? this.ttl : parseTtl(options.ttl)),
|
|
4598
|
+
options.ttl !== void 0
|
|
4581
4599
|
]);
|
|
4582
4600
|
if (rows.length === 0) throw new Error(`Cannot increment non-numeric value for key: ${(0, _warlock_js_logger.maskEmailsInText)(parsedKey)}`);
|
|
4583
4601
|
const newValue = Number(this.decodeJson(rows[0].value));
|
|
@@ -4845,7 +4863,7 @@ const REDIS_GET_DEL_SCRIPT = `local v = redis.call('GET', KEYS[1])
|
|
|
4845
4863
|
if v then redis.call('DEL', KEYS[1]) end
|
|
4846
4864
|
return v`;
|
|
4847
4865
|
/**
|
|
4848
|
-
* Increment that gives a counter without an expiry
|
|
4866
|
+
* Increment that gives a counter without an expiry its per-call or default TTL,
|
|
4849
4867
|
* in one round trip.
|
|
4850
4868
|
*
|
|
4851
4869
|
* KEYS[1]=key; ARGV[1]=amount; ARGV[2]=ttl seconds ('0' = none).
|
|
@@ -5289,10 +5307,11 @@ var RedisCacheDriver = class extends BaseCacheDriver {
|
|
|
5289
5307
|
* Atomic increment using Redis native INCRBY command
|
|
5290
5308
|
* {@inheritdoc}
|
|
5291
5309
|
*/
|
|
5292
|
-
async increment(key, value = 1) {
|
|
5310
|
+
async increment(key, value = 1, options = {}) {
|
|
5293
5311
|
const parsedKey = this.parseKey(key);
|
|
5294
5312
|
this.log("caching", parsedKey);
|
|
5295
|
-
const
|
|
5313
|
+
const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
|
|
5314
|
+
const defaultTtl = Number.isFinite(ttl) && ttl > 0 ? Math.ceil(ttl) : 0;
|
|
5296
5315
|
const result = defaultTtl > 0 ? Number(await this.client?.eval(REDIS_INCREMENT_SCRIPT, {
|
|
5297
5316
|
keys: [parsedKey],
|
|
5298
5317
|
arguments: [String(value), String(defaultTtl)]
|