@warlock.js/cache 5.19.1 → 5.21.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 +61 -1
- package/cjs/index.cjs +1439 -331
- package/cjs/index.cjs.map +1 -1
- package/esm/cache-manager.d.mts +4 -0
- package/esm/cache-manager.d.mts.map +1 -1
- package/esm/cache-manager.mjs +23 -5
- package/esm/cache-manager.mjs.map +1 -1
- package/esm/cached/auto-key.d.mts +14 -14
- package/esm/cached/auto-key.d.mts.map +1 -1
- package/esm/cached/auto-key.mjs +50 -34
- package/esm/cached/auto-key.mjs.map +1 -1
- package/esm/cached/cached.mjs +4 -0
- package/esm/cached/cached.mjs.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +84 -8
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +152 -42
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.d.mts +15 -0
- package/esm/drivers/file-cache-driver.d.mts.map +1 -1
- package/esm/drivers/file-cache-driver.mjs +68 -18
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/in-memory-tag-index.d.mts +44 -0
- package/esm/drivers/in-memory-tag-index.d.mts.map +1 -0
- package/esm/drivers/in-memory-tag-index.mjs +73 -0
- package/esm/drivers/in-memory-tag-index.mjs.map +1 -0
- package/esm/drivers/index.d.mts +1 -1
- package/esm/drivers/index.mjs +1 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts +43 -0
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +96 -0
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts +87 -30
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-cache-driver.mjs +214 -96
- package/esm/drivers/memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.d.mts +9 -5
- package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.mjs +10 -23
- package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
- package/esm/drivers/mock-cache-driver.d.mts +41 -0
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +105 -8
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/null-cache-driver.d.mts +8 -2
- package/esm/drivers/null-cache-driver.d.mts.map +1 -1
- package/esm/drivers/null-cache-driver.mjs +10 -2
- package/esm/drivers/null-cache-driver.mjs.map +1 -1
- package/esm/drivers/pg-cache-driver.d.mts +77 -3
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +229 -7
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.d.mts +86 -1
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +252 -16
- package/esm/drivers/redis-cache-driver.mjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/index.mjs +2 -2
- package/esm/list/memory-cache-list.d.mts +11 -8
- package/esm/list/memory-cache-list.d.mts.map +1 -1
- package/esm/list/memory-cache-list.mjs +39 -37
- package/esm/list/memory-cache-list.mjs.map +1 -1
- package/esm/scoped-cache.d.mts +9 -3
- package/esm/scoped-cache.d.mts.map +1 -1
- package/esm/scoped-cache.mjs +19 -7
- package/esm/scoped-cache.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +20 -0
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +62 -24
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts +7 -3
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +19 -7
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/esm/types.d.mts +41 -3
- package/esm/types.d.mts.map +1 -1
- package/esm/types.mjs.map +1 -1
- package/esm/utils.d.mts +9 -3
- package/esm/utils.d.mts.map +1 -1
- package/esm/utils.mjs +16 -6
- package/esm/utils.mjs.map +1 -1
- package/llms-full.txt +1685 -1666
- package/llms.txt +18 -18
- package/package.json +4 -4
- package/skills/apply-cache-patterns/SKILL.md +98 -97
- package/skills/configure-pg-cache/SKILL.md +6 -5
- package/skills/configure-set-options/SKILL.md +102 -96
- package/skills/handle-cache-errors/SKILL.md +92 -92
- package/skills/index.md +20 -0
- package/skills/observe-cache/SKILL.md +103 -103
- package/skills/pick-cache-driver/SKILL.md +129 -123
- package/skills/test-cache-code/SKILL.md +219 -219
- package/skills/use-cache-atomic/SKILL.md +57 -65
- package/skills/use-cache-bulk/SKILL.md +57 -57
- package/skills/use-cache-list/SKILL.md +85 -85
- package/skills/use-cache-lock/SKILL.md +113 -104
- package/skills/use-cache-namespace/SKILL.md +88 -88
- package/skills/use-cache-similarity/SKILL.md +94 -94
- package/skills/use-cache-tags/SKILL.md +7 -4
- package/skills/use-cache-update-merge/SKILL.md +84 -84
- package/skills/use-cache-utils/SKILL.md +89 -89
- package/skills/use-cached-hof/SKILL.md +100 -99
- package/skills/use-swr/SKILL.md +104 -104
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
* driver overrides `list()` to return a native-command accessor instead.
|
|
8
8
|
*
|
|
9
9
|
* **Role.** Fallback list accessor bound to a driver + key. Every mutation
|
|
10
|
-
*
|
|
10
|
+
* runs through `driver.update()`, so it is serialized in-process and keeps the
|
|
11
|
+
* entry's remaining TTL.
|
|
11
12
|
*
|
|
12
13
|
* **Responsibility.**
|
|
13
14
|
* - Owns: translating list operations into array mutations + driver writes.
|
|
14
|
-
* - Does NOT own: concurrency control (callers should wrap in a
|
|
15
|
-
* lock when multi-process writers are possible),
|
|
16
|
-
*
|
|
15
|
+
* - Does NOT own: cross-process concurrency control (callers should wrap in a
|
|
16
|
+
* distributed lock when multi-process writers are possible), or tagging of
|
|
17
|
+
* list entries.
|
|
17
18
|
*
|
|
18
19
|
* @example
|
|
19
20
|
* // Never constructed directly — obtained via driver.list():
|
|
@@ -33,53 +34,48 @@ var MemoryCacheList = class {
|
|
|
33
34
|
return Array.isArray(current) ? [...current] : [];
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
37
|
+
* Serialized read-modify-write through `driver.update()`. No ttl is passed,
|
|
38
|
+
* so the driver keeps the entry's remaining TTL. An empty list removes the
|
|
39
|
+
* entry to keep the store clean.
|
|
38
40
|
*/
|
|
39
|
-
async
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
async mutate(mutation) {
|
|
42
|
+
let result;
|
|
43
|
+
await this.driver.update(this.key, (current) => {
|
|
44
|
+
const items = Array.isArray(current) ? [...current] : [];
|
|
45
|
+
result = mutation(items);
|
|
46
|
+
return items.length === 0 ? null : items;
|
|
47
|
+
});
|
|
48
|
+
return result;
|
|
45
49
|
}
|
|
46
50
|
/**
|
|
47
51
|
* {@inheritdoc}
|
|
48
52
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
push(...items) {
|
|
54
|
+
return this.mutate((current) => {
|
|
55
|
+
current.push(...items);
|
|
56
|
+
return current.length;
|
|
57
|
+
});
|
|
54
58
|
}
|
|
55
59
|
/**
|
|
56
60
|
* {@inheritdoc}
|
|
57
61
|
*/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
unshift(...items) {
|
|
63
|
+
return this.mutate((current) => {
|
|
64
|
+
current.unshift(...items);
|
|
65
|
+
return current.length;
|
|
66
|
+
});
|
|
63
67
|
}
|
|
64
68
|
/**
|
|
65
69
|
* {@inheritdoc}
|
|
66
70
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (current.length === 0) return null;
|
|
70
|
-
const value = current.pop();
|
|
71
|
-
await this.write(current);
|
|
72
|
-
return value;
|
|
71
|
+
pop() {
|
|
72
|
+
return this.mutate((current) => current.length === 0 ? null : current.pop());
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
75
|
* {@inheritdoc}
|
|
76
76
|
*/
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (current.length === 0) return null;
|
|
80
|
-
const value = current.shift();
|
|
81
|
-
await this.write(current);
|
|
82
|
-
return value;
|
|
77
|
+
shift() {
|
|
78
|
+
return this.mutate((current) => current.length === 0 ? null : current.shift());
|
|
83
79
|
}
|
|
84
80
|
/**
|
|
85
81
|
* {@inheritdoc}
|
|
@@ -100,11 +96,17 @@ var MemoryCacheList = class {
|
|
|
100
96
|
return (await this.read()).length;
|
|
101
97
|
}
|
|
102
98
|
/**
|
|
103
|
-
*
|
|
99
|
+
* Keep the inclusive range `[start, end]` like Redis LTRIM; negative
|
|
100
|
+
* indexes count from the end.
|
|
104
101
|
*/
|
|
105
102
|
async trim(start, end) {
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
await this.mutate((current) => {
|
|
104
|
+
const length = current.length;
|
|
105
|
+
const from = start < 0 ? Math.max(length + start, 0) : start;
|
|
106
|
+
const to = end < 0 ? length + end : end;
|
|
107
|
+
const kept = current.slice(from, to + 1);
|
|
108
|
+
current.splice(0, current.length, ...kept);
|
|
109
|
+
});
|
|
108
110
|
}
|
|
109
111
|
/**
|
|
110
112
|
* {@inheritdoc}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-cache-list.mjs","names":[],"sources":["../../../../../../../cache/src/list/memory-cache-list.ts"],"sourcesContent":["import type { CacheDriver, CacheKey, CacheListAccessor } from \"../types\";\n\n/**\n * Generic array-backed {@link CacheListAccessor}.\n *\n * Stores the full list as a single cache entry and performs read-mutate-write\n * for every operation. Correct for any driver, but O(n) per op. The Redis\n * driver overrides `list()` to return a native-command accessor instead.\n *\n * **Role.** Fallback list accessor bound to a driver + key. Every mutation\n *
|
|
1
|
+
{"version":3,"file":"memory-cache-list.mjs","names":[],"sources":["../../../../../../../cache/src/list/memory-cache-list.ts"],"sourcesContent":["import type { CacheDriver, CacheKey, CacheListAccessor } from \"../types\";\n\n/**\n * Generic array-backed {@link CacheListAccessor}.\n *\n * Stores the full list as a single cache entry and performs read-mutate-write\n * for every operation. Correct for any driver, but O(n) per op. The Redis\n * driver overrides `list()` to return a native-command accessor instead.\n *\n * **Role.** Fallback list accessor bound to a driver + key. Every mutation\n * runs through `driver.update()`, so it is serialized in-process and keeps the\n * entry's remaining TTL.\n *\n * **Responsibility.**\n * - Owns: translating list operations into array mutations + driver writes.\n * - Does NOT own: cross-process concurrency control (callers should wrap in a\n * distributed lock when multi-process writers are possible), or tagging of\n * list entries.\n *\n * @example\n * // Never constructed directly — obtained via driver.list():\n * const list = cache.list<Event>(\"recent-events\");\n * await list.push(event);\n */\nexport class MemoryCacheList<T> implements CacheListAccessor<T> {\n public constructor(\n private readonly driver: CacheDriver<any, any>,\n private readonly key: CacheKey,\n ) {}\n\n /**\n * Read the backing array from the driver. Returns an empty array on miss.\n */\n private async read(): Promise<T[]> {\n const current = (await this.driver.get(this.key)) as T[] | null;\n return Array.isArray(current) ? [...current] : [];\n }\n\n /**\n * Serialized read-modify-write through `driver.update()`. No ttl is passed,\n * so the driver keeps the entry's remaining TTL. An empty list removes the\n * entry to keep the store clean.\n */\n private async mutate<R>(mutation: (items: T[]) => R): Promise<R> {\n let result!: R;\n\n await this.driver.update<T[]>(this.key, current => {\n const items = Array.isArray(current) ? [...current] : [];\n\n result = mutation(items);\n\n return items.length === 0 ? null : items;\n });\n\n return result;\n }\n\n /**\n * {@inheritdoc}\n */\n public push(...items: T[]): Promise<number> {\n return this.mutate(current => {\n current.push(...items);\n\n return current.length;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public unshift(...items: T[]): Promise<number> {\n return this.mutate(current => {\n current.unshift(...items);\n\n return current.length;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public pop(): Promise<T | null> {\n return this.mutate(current => (current.length === 0 ? null : (current.pop() as T)));\n }\n\n /**\n * {@inheritdoc}\n */\n public shift(): Promise<T | null> {\n return this.mutate(current => (current.length === 0 ? null : (current.shift() as T)));\n }\n\n /**\n * {@inheritdoc}\n */\n public async slice(start?: number, end?: number): Promise<T[]> {\n const current = await this.read();\n return current.slice(start, end);\n }\n\n /**\n * {@inheritdoc}\n */\n public async all(): Promise<T[]> {\n return this.read();\n }\n\n /**\n * {@inheritdoc}\n */\n public async length(): Promise<number> {\n const current = await this.read();\n return current.length;\n }\n\n /**\n * Keep the inclusive range `[start, end]` like Redis LTRIM; negative\n * indexes count from the end.\n */\n public async trim(start: number, end: number): Promise<void> {\n await this.mutate(current => {\n const length = current.length;\n const from = start < 0 ? Math.max(length + start, 0) : start;\n const to = end < 0 ? length + end : end;\n const kept = current.slice(from, to + 1);\n\n current.splice(0, current.length, ...kept);\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async clear(): Promise<void> {\n await this.driver.remove(this.key);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwBA,IAAa,kBAAb,MAAgE;CAC9D,AAAO,YACL,AAAiB,QACjB,AAAiB,KACjB;EAFiB;EACA;CAChB;;;;CAKH,MAAc,OAAqB;EACjC,MAAM,UAAW,MAAM,KAAK,OAAO,IAAI,KAAK,GAAG;EAC/C,OAAO,MAAM,QAAQ,OAAO,IAAI,CAAC,GAAG,OAAO,IAAI,CAAC;CAClD;;;;;;CAOA,MAAc,OAAU,UAAyC;EAC/D,IAAI;EAEJ,MAAM,KAAK,OAAO,OAAY,KAAK,MAAK,YAAW;GACjD,MAAM,QAAQ,MAAM,QAAQ,OAAO,IAAI,CAAC,GAAG,OAAO,IAAI,CAAC;GAEvD,SAAS,SAAS,KAAK;GAEvB,OAAO,MAAM,WAAW,IAAI,OAAO;EACrC,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAO,KAAK,GAAG,OAA6B;EAC1C,OAAO,KAAK,QAAO,YAAW;GAC5B,QAAQ,KAAK,GAAG,KAAK;GAErB,OAAO,QAAQ;EACjB,CAAC;CACH;;;;CAKA,AAAO,QAAQ,GAAG,OAA6B;EAC7C,OAAO,KAAK,QAAO,YAAW;GAC5B,QAAQ,QAAQ,GAAG,KAAK;GAExB,OAAO,QAAQ;EACjB,CAAC;CACH;;;;CAKA,AAAO,MAAyB;EAC9B,OAAO,KAAK,QAAO,YAAY,QAAQ,WAAW,IAAI,OAAQ,QAAQ,IAAI,CAAQ;CACpF;;;;CAKA,AAAO,QAA2B;EAChC,OAAO,KAAK,QAAO,YAAY,QAAQ,WAAW,IAAI,OAAQ,QAAQ,MAAM,CAAQ;CACtF;;;;CAKA,MAAa,MAAM,OAAgB,KAA4B;EAE7D,QAAO,MADe,KAAK,KAAK,EAClB,CAAC,MAAM,OAAO,GAAG;CACjC;;;;CAKA,MAAa,MAAoB;EAC/B,OAAO,KAAK,KAAK;CACnB;;;;CAKA,MAAa,SAA0B;EAErC,QAAO,MADe,KAAK,KAAK,EAClB,CAAC;CACjB;;;;;CAMA,MAAa,KAAK,OAAe,KAA4B;EAC3D,MAAM,KAAK,QAAO,YAAW;GAC3B,MAAM,SAAS,QAAQ;GACvB,MAAM,OAAO,QAAQ,IAAI,KAAK,IAAI,SAAS,OAAO,CAAC,IAAI;GACvD,MAAM,KAAK,MAAM,IAAI,SAAS,MAAM;GACpC,MAAM,OAAO,QAAQ,MAAM,MAAM,KAAK,CAAC;GAEvC,QAAQ,OAAO,GAAG,QAAQ,QAAQ,GAAG,IAAI;EAC3C,CAAC;CACH;;;;CAKA,MAAa,QAAuB;EAClC,MAAM,KAAK,OAAO,OAAO,KAAK,GAAG;CACnC;AACF"}
|
package/esm/scoped-cache.d.mts
CHANGED
|
@@ -144,9 +144,10 @@ declare class ScopedCache implements ScopedCacheContract {
|
|
|
144
144
|
*/
|
|
145
145
|
decrement(key: CacheKey, value?: number): Promise<number>;
|
|
146
146
|
/**
|
|
147
|
-
* Atomic read-modify-write.
|
|
148
|
-
*
|
|
149
|
-
* an
|
|
147
|
+
* Atomic read-modify-write. An explicit `options.ttl` always wins. Otherwise
|
|
148
|
+
* the scope's default `ttl` is applied only when the key does not exist yet;
|
|
149
|
+
* for an existing key no ttl is passed so the source preserves the entry's
|
|
150
|
+
* remaining TTL.
|
|
150
151
|
*/
|
|
151
152
|
update<T = any>(key: CacheKey, fn: (current: T | null) => T | null | Promise<T | null>, options?: {
|
|
152
153
|
ttl?: CacheTtl;
|
|
@@ -158,6 +159,11 @@ declare class ScopedCache implements ScopedCacheContract {
|
|
|
158
159
|
merge<T extends Record<string, any> = Record<string, any>>(key: CacheKey, partial: Partial<T>, options?: {
|
|
159
160
|
ttl?: CacheTtl;
|
|
160
161
|
}): Promise<T>;
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the ttl options for update/merge: explicit ttl, else the scope
|
|
164
|
+
* default only for keys that don't exist yet, else nothing (keep remaining).
|
|
165
|
+
*/
|
|
166
|
+
private writeOptions;
|
|
161
167
|
/**
|
|
162
168
|
* Return a list accessor bound to the scoped key. The accessor itself
|
|
163
169
|
* does its own read-mutate-write under the prefixed entry.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scoped-cache.d.mts","names":[],"sources":["../../../../../../cache/src/scoped-cache.ts"],"mappings":";;;;;AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,WAAA,YAAuB,mBAAA;EA0IqB;;;;;EAAA,SApIvC,MAAA;EAyJc;;;;;;;EAAA,SAhJd,MAAA,EAAQ,WAAA;EA2KjB;;;;;;EAAA,SAnKS,QAAA,EAAU,qBAAA;EAmLuB;;;;cA5K/C,MAAA,EAAQ,WAAA,YACR,MAAA,UACA,QAAA,GAAU,qBAAA;
|
|
1
|
+
{"version":3,"file":"scoped-cache.d.mts","names":[],"sources":["../../../../../../cache/src/scoped-cache.ts"],"mappings":";;;;;AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,WAAA,YAAuB,mBAAA;EA0IqB;;;;;EAAA,SApIvC,MAAA;EAyJc;;;;;;;EAAA,SAhJd,MAAA,EAAQ,WAAA;EA2KjB;;;;;;EAAA,SAnKS,QAAA,EAAU,qBAAA;EAmLuB;;;;cA5K/C,MAAA,EAAQ,WAAA,YACR,MAAA,UACA,QAAA,GAAU,qBAAA;EA8LiB;;;;;;;;;EA3KtB,SAAA,CAAU,MAAA,UAAgB,OAAA,GAAS,qBAAA,GAA6B,mBAAA;EAyL5D;;;;;EA3KJ,IAAA,CAAK,IAAA,aAAiB,yBAAA;EA0MQ;;;;;EAjM9B,KAAA,IAAS,OAAA;EA4MJ;;;EArML,GAAA,UAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EA8NjC;;;EAvNJ,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EAsOF;;;EA/NlB,IAAA,CAAK,IAAA,EAAM,QAAA,KAAa,OAAA;EAyQtB;;;EAlQF,IAAA,UAAc,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAtGX;;;;;EA+G3B,GAAA,CACL,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EApGqB;;;;;EA6GjB,OAAA,CAAQ,KAAA,EAAO,MAAA,eAAqB,GAAA,YAAe,OAAA;EA7FxD;;;;EA2GK,KAAA,CAAM,GAAA,EAAK,QAAA,EAAU,KAAA,OAAY,GAAA,YAAe,OAAA;EAvFb;;;;EAqGnC,OAAA,UAAiB,GAAA,EAAK,QAAA,EAAU,KAAA,EAAO,CAAA,GAAI,OAAA,CAAQ,CAAA;EAvF7B;;;EA8FtB,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EA9EnB;;;;EAsFJ,QAAA,UACL,GAAA,EAAK,QAAA,EACL,YAAA,EAAc,QAAA,GAAW,eAAA,EACzB,QAAA,QAAgB,OAAA,CAAQ,CAAA,IACvB,OAAA,CAAQ,CAAA;EAnFJ;;;;;;EAiGA,GAAA,UACL,GAAA,EAAK,QAAA,EACL,OAAA,EAAS,eAAA,EACT,QAAA,QAAgB,OAAA,CAAQ,CAAA,IACvB,OAAA,CAAQ,CAAA;EA9FoB;;;;EA2GxB,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAiB,OAAA;EApGZ;;;;EA4G9B,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAiB,OAAA;EAjG/C;;;;;;EA2GW,MAAA,UACX,GAAA,EAAK,QAAA,EACL,EAAA,GAAK,OAAA,EAAS,CAAA,YAAa,CAAA,UAAW,OAAA,CAAQ,CAAA,UAC9C,OAAA;IAAY,GAAA,GAAM,QAAA;EAAA,IACjB,OAAA,CAAQ,CAAA;EApG+C;;;;EA8G7C,KAAA,WAAgB,MAAA,gBAAsB,MAAA,eACjD,GAAA,EAAK,QAAA,EACL,OAAA,EAAS,OAAA,CAAQ,CAAA,GACjB,OAAA;IAAY,GAAA,GAAM,QAAA;EAAA,IACjB,OAAA,CAAQ,CAAA;EAtFJ;;;;EAAA,QAgGO,YAAA;EAhGyB;;;;EAmHhC,IAAA,UAAc,GAAA,EAAK,QAAA,GAAW,iBAAA,CAAkB,CAAA;EA5GzC;;;;EAoHP,IAAA,IACL,GAAA,EAAK,QAAA,EACL,YAAA,EAAc,QAAA,GAAW,IAAA,CAAK,WAAA,aAC9B,EAAA,QAAU,OAAA,CAAQ,CAAA,IACjB,OAAA,CAAQ,WAAA,CAAY,CAAA;EA/GrB;;;;;;EAqIW,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;EArIxB;;;;;EAAA,UAmJO,SAAA,CAAU,GAAA,EAAK,QAAA;EAnId;;;;;;EAAA,UAmJD,eAAA,CACR,KAAA,GAAQ,QAAA,GAAW,eAAA,GAClB,eAAA;EAtII;;;;EAAA,UA6JG,oBAAA,CACR,KAAA,EAAO,QAAA,GAAW,eAAA,GACjB,QAAA,GAAW,eAAA;EAvJP;;;;;EAAA,UA8KG,eAAA;AAAA"}
|
package/esm/scoped-cache.mjs
CHANGED
|
@@ -174,19 +174,31 @@ var ScopedCache = class ScopedCache {
|
|
|
174
174
|
return this.source.decrement(this.scopedKey(key), value);
|
|
175
175
|
}
|
|
176
176
|
/**
|
|
177
|
-
* Atomic read-modify-write.
|
|
178
|
-
*
|
|
179
|
-
* an
|
|
177
|
+
* Atomic read-modify-write. An explicit `options.ttl` always wins. Otherwise
|
|
178
|
+
* the scope's default `ttl` is applied only when the key does not exist yet;
|
|
179
|
+
* for an existing key no ttl is passed so the source preserves the entry's
|
|
180
|
+
* remaining TTL.
|
|
180
181
|
*/
|
|
181
|
-
update(key, fn, options) {
|
|
182
|
-
|
|
182
|
+
async update(key, fn, options) {
|
|
183
|
+
const scopedKey = this.scopedKey(key);
|
|
184
|
+
return this.source.update(scopedKey, fn, await this.writeOptions(scopedKey, options));
|
|
183
185
|
}
|
|
184
186
|
/**
|
|
185
187
|
* Shallow-merge a partial object into the scoped entry. Same TTL semantics
|
|
186
188
|
* as {@link update}.
|
|
187
189
|
*/
|
|
188
|
-
merge(key, partial, options) {
|
|
189
|
-
|
|
190
|
+
async merge(key, partial, options) {
|
|
191
|
+
const scopedKey = this.scopedKey(key);
|
|
192
|
+
return this.source.merge(scopedKey, partial, await this.writeOptions(scopedKey, options));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Resolve the ttl options for update/merge: explicit ttl, else the scope
|
|
196
|
+
* default only for keys that don't exist yet, else nothing (keep remaining).
|
|
197
|
+
*/
|
|
198
|
+
async writeOptions(scopedKey, options) {
|
|
199
|
+
if (options?.ttl !== void 0) return { ttl: options.ttl };
|
|
200
|
+
if (this.defaults.ttl === void 0) return {};
|
|
201
|
+
return await this.source.has(scopedKey) ? {} : { ttl: this.defaults.ttl };
|
|
190
202
|
}
|
|
191
203
|
/**
|
|
192
204
|
* Return a list accessor bound to the scoped key. The accessor itself
|
package/esm/scoped-cache.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scoped-cache.mjs","names":[],"sources":["../../../../../../cache/src/scoped-cache.ts"],"sourcesContent":["import { TaggedScopedCache } from \"./tagged-scoped-cache\";\nimport type {\n CacheDriver,\n CacheKey,\n CacheListAccessor,\n CacheNamespaceOptions,\n CacheSetOptions,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheSwrOptions,\n CacheTtl,\n LockOptions,\n LockOutcome,\n RememberOptions,\n ScopedCacheContract,\n TaggedScopedCacheContract,\n} from \"./types\";\nimport {\n mergeTagSets,\n normalizeToOptions,\n normalizeToRememberOptions,\n parseCacheKey,\n parseTtl,\n} from \"./utils\";\n\n/**\n * Scoped view over a cache source. Returned by `cache.namespace(prefix, options?)`.\n *\n * **Role.** A `ScopedCache` is a stateless wrapper that prepends a fixed\n * prefix to every key and applies optional default `ttl` / `tags` to every\n * write. Stores nothing itself — every call forwards to the underlying\n * `source` (typically the `CacheManager`, but any `CacheDriver` works).\n *\n * **Responsibility.**\n * - Owns: prefix-prepending of keys, normalization of nested-scope prefixes,\n * merging scope defaults into write options (`ttl`, `tags`), filtering\n * `similar()` hits to its own scope, and exposing `.clear()` as a sugar\n * for `removeNamespace(prefix)`.\n * - Does NOT own: actual storage, connection lifecycle, event listeners,\n * driver selection, or tag-index bookkeeping (delegated to the source's\n * tagged-cache machinery).\n *\n * Per-call options always win over scope defaults; tags merge additively\n * across (scope defaults + per-call) layers. Nested scopes inherit and may\n * override the parent's defaults — see {@link ScopedCache.namespace}.\n *\n * @example\n * const chat = cache.namespace(`chats.${id}`, { ttl: \"30d\" });\n *\n * await chat.set(\"messages.10\", msg); // 30d default\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call override\n * await chat.namespace(\"typing\", { ttl: \"5s\" }).set(\"user.42\", true);\n * await chat.clear();\n */\nexport class ScopedCache implements ScopedCacheContract {\n /**\n * Fully-qualified prefix prepended to every key handled by this scope.\n * Normalized through {@link parseCacheKey} on construction so colon-form\n * input (`\"chats:10\"`) and trailing dots compose cleanly with nested scopes.\n */\n public readonly prefix: string;\n\n /**\n * Underlying cache source. Public-readonly so co-located helpers\n * (`TaggedScopedCache`) can delegate without ceremony — not part of the\n * stable consumer API.\n *\n * @internal\n */\n public readonly source: CacheDriver<any, any>;\n\n /**\n * Defaults applied to every write through this scope. Per-call options\n * override `ttl`; `tags` merge additively across layers.\n *\n * @internal\n */\n public readonly defaults: CacheNamespaceOptions;\n\n /**\n * Build a scope. Constructed via `cache.namespace(prefix, options)` —\n * users never call this directly.\n */\n public constructor(\n source: CacheDriver<any, any>,\n prefix: string,\n defaults: CacheNamespaceOptions = {},\n ) {\n this.source = source;\n this.prefix = parseCacheKey(prefix);\n this.defaults = {\n ttl: defaults.ttl,\n tags: defaults.tags && defaults.tags.length > 0 ? [...defaults.tags] : undefined,\n };\n }\n\n /**\n * Build a nested scope. The child's prefix is `parent.child`; child options\n * override the parent's `ttl` and union into `tags`.\n *\n * @example\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\n * const typing = chat.namespace(\"typing\", { ttl: \"5s\" });\n * // typing.prefix === \"chats.10.typing\"\n */\n public namespace(prefix: string, options: CacheNamespaceOptions = {}): ScopedCacheContract {\n const childPrefix = `${this.prefix}.${parseCacheKey(prefix)}`;\n\n return new ScopedCache(this.source, childPrefix, {\n ttl: options.ttl ?? this.defaults.ttl,\n tags: mergeTagSets(this.defaults.tags, options.tags),\n });\n }\n\n /**\n * Return a one-shot tagged write handle. The handle's tags merge additively\n * with scope-level defaults — final tag list per write is the union of\n * (scope tags + handle tags + per-call tags), deduped.\n */\n public tags(tags: string[]): TaggedScopedCacheContract {\n return new TaggedScopedCache(this, tags);\n }\n\n /**\n * Wipe every entry under this scope's prefix. Sugar over\n * `source.removeNamespace(prefix)` — siblings outside the scope are\n * untouched.\n */\n public clear(): Promise<void> {\n return this.source.removeNamespace(this.prefix);\n }\n\n /**\n * Read the value at the scoped key. Forwards to the source after prefixing.\n */\n public get<T = any>(key: CacheKey): Promise<T | null> {\n return this.source.get<T>(this.scopedKey(key));\n }\n\n /**\n * Check presence of the scoped key without fetching the value.\n */\n public has(key: CacheKey): Promise<boolean> {\n return this.source.has(this.scopedKey(key));\n }\n\n /**\n * Batch-read scoped keys. Each input key is prefixed before forwarding.\n */\n public many(keys: CacheKey[]): Promise<any[]> {\n return this.source.many(keys.map((key) => this.scopedKey(key)));\n }\n\n /**\n * Read-and-remove. Returns the value or `null`; the entry is gone after.\n */\n public pull<T = any>(key: CacheKey): Promise<T | null> {\n return this.source.pull<T>(this.scopedKey(key));\n }\n\n /**\n * Write the scoped key. Per-call `ttl`/`tags` win over scope defaults;\n * `expiresAt` is preserved as-is (absolute deadlines are never overridden\n * by the scope's relative-ttl default).\n */\n public set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n return this.source.set(this.scopedKey(key), value, this.mergeSetOptions(ttlOrOptions));\n }\n\n /**\n * Batch-write under the scope. Caller's positional `ttl` wins; otherwise\n * the scope default is parsed to seconds (since `setMany` accepts only a\n * numeric ttl).\n */\n public setMany(items: Record<string, any>, ttl?: number): Promise<void> {\n const scoped: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(items)) {\n scoped[this.scopedKey(key)] = value;\n }\n\n return this.source.setMany(scoped, ttl ?? this.scopeTtlSeconds());\n }\n\n /**\n * Atomic create-or-skip on the scoped key. Throws when the underlying\n * source has no `setNX` (driver-specific — Redis-only today).\n */\n public setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n if (!this.source.setNX) {\n throw new Error(\n `setNX is not supported by the underlying cache source: ${this.source.name}`,\n );\n }\n\n return this.source.setNX(this.scopedKey(key), value, ttl ?? this.scopeTtlSeconds());\n }\n\n /**\n * Permanent write (no expiration). Bypasses the scope's `ttl` default —\n * `forever` always means forever, regardless of scope policy.\n */\n public forever<T = any>(key: CacheKey, value: T): Promise<T> {\n return this.source.forever<T>(this.scopedKey(key), value);\n }\n\n /**\n * Remove a single scoped key.\n */\n public remove(key: CacheKey): Promise<void> {\n return this.source.remove(this.scopedKey(key));\n }\n\n /**\n * Read-or-compute. Cache-miss writes pick up the scope's default `ttl`\n * and `tags` unless the caller passed an options object that overrides.\n */\n public remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n return this.source.remember<T>(\n this.scopedKey(key),\n this.mergeRememberOptions(ttlOrOptions),\n callback,\n );\n }\n\n /**\n * Stale-while-revalidate on the scoped key. Scope-level `tags` merge\n * additively with `options.tags`; `freshTtl`/`staleTtl` always come from\n * the caller (no scope-default precedence — the SWR shape is too\n * specific to the call site to inherit).\n */\n public swr<T = any>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n const merged: CacheSwrOptions = {\n ...options,\n tags: mergeTagSets(this.defaults.tags, options.tags),\n };\n\n return this.source.swr<T>(this.scopedKey(key), merged, callback);\n }\n\n /**\n * Atomic counter increment on the scoped key. TTL is preserved by the\n * underlying driver — scope ttl is only applied on first write via `set`.\n */\n public increment(key: CacheKey, value?: number): Promise<number> {\n return this.source.increment(this.scopedKey(key), value);\n }\n\n /**\n * Atomic counter decrement on the scoped key. See {@link increment} for\n * TTL semantics.\n */\n public decrement(key: CacheKey, value?: number): Promise<number> {\n return this.source.decrement(this.scopedKey(key), value);\n }\n\n /**\n * Atomic read-modify-write. Falls back to the scope's `ttl` when the caller\n * doesn't provide one; the source still keeps the existing entry's TTL on\n * an update unless `options.ttl` is explicitly set.\n */\n public update<T = any>(\n key: CacheKey,\n fn: (current: T | null) => T | null | Promise<T | null>,\n options?: { ttl?: CacheTtl },\n ): Promise<T | null> {\n return this.source.update<T>(this.scopedKey(key), fn, {\n ttl: options?.ttl ?? this.defaults.ttl,\n });\n }\n\n /**\n * Shallow-merge a partial object into the scoped entry. Same TTL semantics\n * as {@link update}.\n */\n public merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T> {\n return this.source.merge<T>(this.scopedKey(key), partial, {\n ttl: options?.ttl ?? this.defaults.ttl,\n });\n }\n\n /**\n * Return a list accessor bound to the scoped key. The accessor itself\n * does its own read-mutate-write under the prefixed entry.\n */\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\n return this.source.list<T>(this.scopedKey(key));\n }\n\n /**\n * Acquire a distributed lock on the scoped key. Caller's TTL wins; when\n * the options form omits `ttl`, the scope default fills in.\n */\n public lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>> {\n if (typeof ttlOrOptions === \"object\" && ttlOrOptions !== null) {\n const merged: Omit<LockOptions, \"driver\"> = {\n ...ttlOrOptions,\n ttl: ttlOrOptions.ttl ?? this.defaults.ttl ?? ttlOrOptions.ttl,\n };\n\n // The third coalesce is intentional — when both caller and scope omit\n // the TTL, we still pass the original (undefined) through so the source\n // raises its own validation error rather than us swallowing it silently.\n return this.source.lock<T>(this.scopedKey(key), merged, fn);\n }\n\n return this.source.lock<T>(this.scopedKey(key), ttlOrOptions, fn);\n }\n\n /**\n * Similarity retrieval, scope-isolated. Hits whose keys fall outside this\n * scope are filtered out before the result is returned. `topK` applies to\n * the underlying retrieval — when the scope contains fewer than `topK`\n * matches but other scopes do, the caller will see fewer hits than `topK`.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const hits = await this.source.similar<T>(vector, options);\n const parsedPrefix = this.source.parseKey(this.prefix);\n\n return hits.filter(\n (hit) => hit.key === parsedPrefix || hit.key.startsWith(parsedPrefix + \".\"),\n );\n }\n\n /**\n * Build the source-side key by prepending the scope prefix. Object keys\n * are normalized via {@link parseCacheKey} first so they compose with the\n * prefix as plain dot-strings.\n */\n protected scopedKey(key: CacheKey): string {\n const keyString = typeof key === \"string\" ? key : parseCacheKey(key);\n\n if (!keyString) {\n return this.prefix;\n }\n\n return `${this.prefix}.${keyString}`;\n }\n\n /**\n * Coerce the polymorphic 3rd `set` argument into a {@link CacheSetOptions}\n * with scope defaults filled in. Per-call values always win; tags merge\n * additively. `expiresAt` is preserved without injecting the scope's `ttl`\n * default (absolute deadlines override relative ones).\n */\n protected mergeSetOptions(\n input?: CacheTtl | CacheSetOptions,\n ): CacheSetOptions | undefined {\n const options = normalizeToOptions(input);\n const ttl =\n options.ttl ?? (options.expiresAt === undefined ? this.defaults.ttl : undefined);\n const tags = mergeTagSets(this.defaults.tags, options.tags);\n\n const merged: CacheSetOptions = { ...options };\n\n if (ttl !== undefined) {\n merged.ttl = ttl;\n }\n\n if (tags !== undefined) {\n merged.tags = tags;\n }\n\n return merged;\n }\n\n /**\n * Same merge as {@link mergeSetOptions} but for the `remember()` shape\n * ({@link RememberOptions} — no `expiresAt`).\n */\n protected mergeRememberOptions(\n input: CacheTtl | RememberOptions,\n ): CacheTtl | RememberOptions {\n const options = normalizeToRememberOptions(input);\n const ttl = options.ttl ?? this.defaults.ttl;\n const tags = mergeTagSets(this.defaults.tags, options.tags);\n\n const merged: RememberOptions = { ...options };\n\n if (ttl !== undefined) {\n merged.ttl = ttl;\n }\n\n if (tags !== undefined) {\n merged.tags = tags;\n }\n\n return merged;\n }\n\n /**\n * Convert the scope's default `ttl` (which may be a duration string) into\n * seconds, for the few methods (`setMany`, `setNX`) that accept only a\n * numeric ttl.\n */\n protected scopeTtlSeconds(): number | undefined {\n if (this.defaults.ttl === undefined) {\n return undefined;\n }\n\n return parseTtl(this.defaults.ttl);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scoped-cache.mjs","names":[],"sources":["../../../../../../cache/src/scoped-cache.ts"],"sourcesContent":["import { TaggedScopedCache } from \"./tagged-scoped-cache\";\nimport type {\n CacheDriver,\n CacheKey,\n CacheListAccessor,\n CacheNamespaceOptions,\n CacheSetOptions,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheSwrOptions,\n CacheTtl,\n LockOptions,\n LockOutcome,\n RememberOptions,\n ScopedCacheContract,\n TaggedScopedCacheContract,\n} from \"./types\";\nimport {\n mergeTagSets,\n normalizeToOptions,\n normalizeToRememberOptions,\n parseCacheKey,\n parseTtl,\n} from \"./utils\";\n\n/**\n * Scoped view over a cache source. Returned by `cache.namespace(prefix, options?)`.\n *\n * **Role.** A `ScopedCache` is a stateless wrapper that prepends a fixed\n * prefix to every key and applies optional default `ttl` / `tags` to every\n * write. Stores nothing itself — every call forwards to the underlying\n * `source` (typically the `CacheManager`, but any `CacheDriver` works).\n *\n * **Responsibility.**\n * - Owns: prefix-prepending of keys, normalization of nested-scope prefixes,\n * merging scope defaults into write options (`ttl`, `tags`), filtering\n * `similar()` hits to its own scope, and exposing `.clear()` as a sugar\n * for `removeNamespace(prefix)`.\n * - Does NOT own: actual storage, connection lifecycle, event listeners,\n * driver selection, or tag-index bookkeeping (delegated to the source's\n * tagged-cache machinery).\n *\n * Per-call options always win over scope defaults; tags merge additively\n * across (scope defaults + per-call) layers. Nested scopes inherit and may\n * override the parent's defaults — see {@link ScopedCache.namespace}.\n *\n * @example\n * const chat = cache.namespace(`chats.${id}`, { ttl: \"30d\" });\n *\n * await chat.set(\"messages.10\", msg); // 30d default\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call override\n * await chat.namespace(\"typing\", { ttl: \"5s\" }).set(\"user.42\", true);\n * await chat.clear();\n */\nexport class ScopedCache implements ScopedCacheContract {\n /**\n * Fully-qualified prefix prepended to every key handled by this scope.\n * Normalized through {@link parseCacheKey} on construction so colon-form\n * input (`\"chats:10\"`) and trailing dots compose cleanly with nested scopes.\n */\n public readonly prefix: string;\n\n /**\n * Underlying cache source. Public-readonly so co-located helpers\n * (`TaggedScopedCache`) can delegate without ceremony — not part of the\n * stable consumer API.\n *\n * @internal\n */\n public readonly source: CacheDriver<any, any>;\n\n /**\n * Defaults applied to every write through this scope. Per-call options\n * override `ttl`; `tags` merge additively across layers.\n *\n * @internal\n */\n public readonly defaults: CacheNamespaceOptions;\n\n /**\n * Build a scope. Constructed via `cache.namespace(prefix, options)` —\n * users never call this directly.\n */\n public constructor(\n source: CacheDriver<any, any>,\n prefix: string,\n defaults: CacheNamespaceOptions = {},\n ) {\n this.source = source;\n this.prefix = parseCacheKey(prefix);\n this.defaults = {\n ttl: defaults.ttl,\n tags: defaults.tags && defaults.tags.length > 0 ? [...defaults.tags] : undefined,\n };\n }\n\n /**\n * Build a nested scope. The child's prefix is `parent.child`; child options\n * override the parent's `ttl` and union into `tags`.\n *\n * @example\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\n * const typing = chat.namespace(\"typing\", { ttl: \"5s\" });\n * // typing.prefix === \"chats.10.typing\"\n */\n public namespace(prefix: string, options: CacheNamespaceOptions = {}): ScopedCacheContract {\n const childPrefix = `${this.prefix}.${parseCacheKey(prefix)}`;\n\n return new ScopedCache(this.source, childPrefix, {\n ttl: options.ttl ?? this.defaults.ttl,\n tags: mergeTagSets(this.defaults.tags, options.tags),\n });\n }\n\n /**\n * Return a one-shot tagged write handle. The handle's tags merge additively\n * with scope-level defaults — final tag list per write is the union of\n * (scope tags + handle tags + per-call tags), deduped.\n */\n public tags(tags: string[]): TaggedScopedCacheContract {\n return new TaggedScopedCache(this, tags);\n }\n\n /**\n * Wipe every entry under this scope's prefix. Sugar over\n * `source.removeNamespace(prefix)` — siblings outside the scope are\n * untouched.\n */\n public clear(): Promise<void> {\n return this.source.removeNamespace(this.prefix);\n }\n\n /**\n * Read the value at the scoped key. Forwards to the source after prefixing.\n */\n public get<T = any>(key: CacheKey): Promise<T | null> {\n return this.source.get<T>(this.scopedKey(key));\n }\n\n /**\n * Check presence of the scoped key without fetching the value.\n */\n public has(key: CacheKey): Promise<boolean> {\n return this.source.has(this.scopedKey(key));\n }\n\n /**\n * Batch-read scoped keys. Each input key is prefixed before forwarding.\n */\n public many(keys: CacheKey[]): Promise<any[]> {\n return this.source.many(keys.map((key) => this.scopedKey(key)));\n }\n\n /**\n * Read-and-remove. Returns the value or `null`; the entry is gone after.\n */\n public pull<T = any>(key: CacheKey): Promise<T | null> {\n return this.source.pull<T>(this.scopedKey(key));\n }\n\n /**\n * Write the scoped key. Per-call `ttl`/`tags` win over scope defaults;\n * `expiresAt` is preserved as-is (absolute deadlines are never overridden\n * by the scope's relative-ttl default).\n */\n public set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n return this.source.set(this.scopedKey(key), value, this.mergeSetOptions(ttlOrOptions));\n }\n\n /**\n * Batch-write under the scope. Caller's positional `ttl` wins; otherwise\n * the scope default is parsed to seconds (since `setMany` accepts only a\n * numeric ttl).\n */\n public setMany(items: Record<string, any>, ttl?: number): Promise<void> {\n const scoped: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(items)) {\n scoped[this.scopedKey(key)] = value;\n }\n\n return this.source.setMany(scoped, ttl ?? this.scopeTtlSeconds());\n }\n\n /**\n * Atomic create-or-skip on the scoped key. Throws when the underlying\n * source has no `setNX` (driver-specific — Redis-only today).\n */\n public setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n if (!this.source.setNX) {\n throw new Error(\n `setNX is not supported by the underlying cache source: ${this.source.name}`,\n );\n }\n\n return this.source.setNX(this.scopedKey(key), value, ttl ?? this.scopeTtlSeconds());\n }\n\n /**\n * Permanent write (no expiration). Bypasses the scope's `ttl` default —\n * `forever` always means forever, regardless of scope policy.\n */\n public forever<T = any>(key: CacheKey, value: T): Promise<T> {\n return this.source.forever<T>(this.scopedKey(key), value);\n }\n\n /**\n * Remove a single scoped key.\n */\n public remove(key: CacheKey): Promise<void> {\n return this.source.remove(this.scopedKey(key));\n }\n\n /**\n * Read-or-compute. Cache-miss writes pick up the scope's default `ttl`\n * and `tags` unless the caller passed an options object that overrides.\n */\n public remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n return this.source.remember<T>(\n this.scopedKey(key),\n this.mergeRememberOptions(ttlOrOptions),\n callback,\n );\n }\n\n /**\n * Stale-while-revalidate on the scoped key. Scope-level `tags` merge\n * additively with `options.tags`; `freshTtl`/`staleTtl` always come from\n * the caller (no scope-default precedence — the SWR shape is too\n * specific to the call site to inherit).\n */\n public swr<T = any>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n const merged: CacheSwrOptions = {\n ...options,\n tags: mergeTagSets(this.defaults.tags, options.tags),\n };\n\n return this.source.swr<T>(this.scopedKey(key), merged, callback);\n }\n\n /**\n * Atomic counter increment on the scoped key. TTL is preserved by the\n * underlying driver — scope ttl is only applied on first write via `set`.\n */\n public increment(key: CacheKey, value?: number): Promise<number> {\n return this.source.increment(this.scopedKey(key), value);\n }\n\n /**\n * Atomic counter decrement on the scoped key. See {@link increment} for\n * TTL semantics.\n */\n public decrement(key: CacheKey, value?: number): Promise<number> {\n return this.source.decrement(this.scopedKey(key), value);\n }\n\n /**\n * Atomic read-modify-write. An explicit `options.ttl` always wins. Otherwise\n * the scope's default `ttl` is applied only when the key does not exist yet;\n * for an existing key no ttl is passed so the source preserves the entry's\n * remaining TTL.\n */\n public async update<T = any>(\n key: CacheKey,\n fn: (current: T | null) => T | null | Promise<T | null>,\n options?: { ttl?: CacheTtl },\n ): Promise<T | null> {\n const scopedKey = this.scopedKey(key);\n\n return this.source.update<T>(scopedKey, fn, await this.writeOptions(scopedKey, options));\n }\n\n /**\n * Shallow-merge a partial object into the scoped entry. Same TTL semantics\n * as {@link update}.\n */\n public async merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T> {\n const scopedKey = this.scopedKey(key);\n\n return this.source.merge<T>(scopedKey, partial, await this.writeOptions(scopedKey, options));\n }\n\n /**\n * Resolve the ttl options for update/merge: explicit ttl, else the scope\n * default only for keys that don't exist yet, else nothing (keep remaining).\n */\n private async writeOptions(\n scopedKey: CacheKey,\n options?: { ttl?: CacheTtl },\n ): Promise<{ ttl?: CacheTtl }> {\n if (options?.ttl !== undefined) {\n return { ttl: options.ttl };\n }\n\n if (this.defaults.ttl === undefined) {\n return {};\n }\n\n return (await this.source.has(scopedKey)) ? {} : { ttl: this.defaults.ttl };\n }\n\n /**\n * Return a list accessor bound to the scoped key. The accessor itself\n * does its own read-mutate-write under the prefixed entry.\n */\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\n return this.source.list<T>(this.scopedKey(key));\n }\n\n /**\n * Acquire a distributed lock on the scoped key. Caller's TTL wins; when\n * the options form omits `ttl`, the scope default fills in.\n */\n public lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>> {\n if (typeof ttlOrOptions === \"object\" && ttlOrOptions !== null) {\n const merged: Omit<LockOptions, \"driver\"> = {\n ...ttlOrOptions,\n ttl: ttlOrOptions.ttl ?? this.defaults.ttl ?? ttlOrOptions.ttl,\n };\n\n // The third coalesce is intentional — when both caller and scope omit\n // the TTL, we still pass the original (undefined) through so the source\n // raises its own validation error rather than us swallowing it silently.\n return this.source.lock<T>(this.scopedKey(key), merged, fn);\n }\n\n return this.source.lock<T>(this.scopedKey(key), ttlOrOptions, fn);\n }\n\n /**\n * Similarity retrieval, scope-isolated. Hits whose keys fall outside this\n * scope are filtered out before the result is returned. `topK` applies to\n * the underlying retrieval — when the scope contains fewer than `topK`\n * matches but other scopes do, the caller will see fewer hits than `topK`.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const hits = await this.source.similar<T>(vector, options);\n const parsedPrefix = this.source.parseKey(this.prefix);\n\n return hits.filter(\n (hit) => hit.key === parsedPrefix || hit.key.startsWith(parsedPrefix + \".\"),\n );\n }\n\n /**\n * Build the source-side key by prepending the scope prefix. Object keys\n * are normalized via {@link parseCacheKey} first so they compose with the\n * prefix as plain dot-strings.\n */\n protected scopedKey(key: CacheKey): string {\n const keyString = typeof key === \"string\" ? key : parseCacheKey(key);\n\n if (!keyString) {\n return this.prefix;\n }\n\n return `${this.prefix}.${keyString}`;\n }\n\n /**\n * Coerce the polymorphic 3rd `set` argument into a {@link CacheSetOptions}\n * with scope defaults filled in. Per-call values always win; tags merge\n * additively. `expiresAt` is preserved without injecting the scope's `ttl`\n * default (absolute deadlines override relative ones).\n */\n protected mergeSetOptions(\n input?: CacheTtl | CacheSetOptions,\n ): CacheSetOptions | undefined {\n const options = normalizeToOptions(input);\n const ttl =\n options.ttl ?? (options.expiresAt === undefined ? this.defaults.ttl : undefined);\n const tags = mergeTagSets(this.defaults.tags, options.tags);\n\n const merged: CacheSetOptions = { ...options };\n\n if (ttl !== undefined) {\n merged.ttl = ttl;\n }\n\n if (tags !== undefined) {\n merged.tags = tags;\n }\n\n return merged;\n }\n\n /**\n * Same merge as {@link mergeSetOptions} but for the `remember()` shape\n * ({@link RememberOptions} — no `expiresAt`).\n */\n protected mergeRememberOptions(\n input: CacheTtl | RememberOptions,\n ): CacheTtl | RememberOptions {\n const options = normalizeToRememberOptions(input);\n const ttl = options.ttl ?? this.defaults.ttl;\n const tags = mergeTagSets(this.defaults.tags, options.tags);\n\n const merged: RememberOptions = { ...options };\n\n if (ttl !== undefined) {\n merged.ttl = ttl;\n }\n\n if (tags !== undefined) {\n merged.tags = tags;\n }\n\n return merged;\n }\n\n /**\n * Convert the scope's default `ttl` (which may be a duration string) into\n * seconds, for the few methods (`setMany`, `setNX`) that accept only a\n * numeric ttl.\n */\n protected scopeTtlSeconds(): number | undefined {\n if (this.defaults.ttl === undefined) {\n return undefined;\n }\n\n return parseTtl(this.defaults.ttl);\n }\n}\n"],"mappings":"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"}
|
package/esm/tagged-cache.d.mts
CHANGED
|
@@ -36,6 +36,22 @@ declare class TaggedCache implements TaggedCacheDriver {
|
|
|
36
36
|
* stable consumer API.
|
|
37
37
|
*/
|
|
38
38
|
storeTagRelationship(key: CacheKey): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Upper bound on member-existence checks per tagged write, so pruning stays
|
|
41
|
+
* O(1) per write no matter how large an index grows.
|
|
42
|
+
*/
|
|
43
|
+
protected static readonly PRUNE_CHECKS_PER_WRITE = 20;
|
|
44
|
+
/**
|
|
45
|
+
* Drop index members whose entry no longer exists (expired, evicted, or
|
|
46
|
+
* removed outside a tagged handle). Checks a random window of at most
|
|
47
|
+
* {@link PRUNE_CHECKS_PER_WRITE} members, so a long-lived index converges
|
|
48
|
+
* to its live members without any write paying for a full scan.
|
|
49
|
+
*/
|
|
50
|
+
protected pruneTag(tagKey: string, justWritten: string): Promise<void>;
|
|
51
|
+
protected addToTag(tagKey: string, members: string[]): Promise<void>;
|
|
52
|
+
protected membersOf(tagKey: string): Promise<string[]>;
|
|
53
|
+
protected removeFromTag(tagKey: string, members: string[]): Promise<void>;
|
|
54
|
+
protected legacyMembers(tagKey: string): Promise<string[]>;
|
|
39
55
|
/**
|
|
40
56
|
* The form a key takes inside a tag index: normalized but un-prefixed.
|
|
41
57
|
*
|
|
@@ -67,6 +83,10 @@ declare class TaggedCache implements TaggedCacheDriver {
|
|
|
67
83
|
* Invalidate (clear) all keys associated with the current tags
|
|
68
84
|
*/
|
|
69
85
|
invalidate(): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Remove `indexedKey` from every tag of this handle.
|
|
88
|
+
*/
|
|
89
|
+
protected detachFromTags(indexedKey: string): Promise<void>;
|
|
70
90
|
/**
|
|
71
91
|
* Flush all keys associated with the current tags
|
|
72
92
|
* @deprecated Use invalidate() instead for better semantics
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tagged-cache.d.mts","names":[],"sources":["../../../../../../cache/src/tagged-cache.ts"],"mappings":";;;;;AAaA;;cAAa,WAAA,YAAuB,iBAAA;EAShB;;;EAAA,UALR,SAAA;EAsC6B;;;EAAA,UAjC7B,MAAA,EAAQ,WAAA;
|
|
1
|
+
{"version":3,"file":"tagged-cache.d.mts","names":[],"sources":["../../../../../../cache/src/tagged-cache.ts"],"mappings":";;;;;AAaA;;cAAa,WAAA,YAAuB,iBAAA;EAShB;;;EAAA,UALR,SAAA;EAsC6B;;;EAAA,UAjC7B,MAAA,EAAQ,WAAA;EAqGyB;;;cAhGxB,IAAA,YAAgB,MAAA,EAAQ,WAAA;EAqJF;;;EAAA,UA7I/B,MAAA,CAAO,GAAA;EA+JW;;;EAAA,UAxJZ,cAAA,CAAe,GAAA,EAAK,QAAA,GAAW,OAAA;EA2KtB;;;;;;;;;EA9JZ,oBAAA,CAAqB,GAAA,EAAK,QAAA,GAAW,OAAA;EA2O3B;;;;EAAA,0BA5NG,sBAAA;EAkPgC;;;;;;EAAA,UA1O1C,QAAA,CAAS,MAAA,UAAgB,WAAA,WAAsB,OAAA;EAAA,UAgC/C,QAAA,CAAS,MAAA,UAAgB,OAAA,aAAoB,OAAA;EAAA,UAa7C,SAAA,CAAU,MAAA,WAAiB,OAAA;EAAA,UAQ3B,aAAA,CAAc,MAAA,UAAgB,OAAA,aAAoB,OAAA;EAAA,UAoBlD,aAAA,CAAc,MAAA,WAAiB,OAAA;EA5H5B;;;;;;;;;;EAAA,UA8IT,UAAA,CAAW,GAAA,EAAK,QAAA;EAlHQ;;;EAAA,UAyHlB,aAAA,IAAiB,OAAA,CAAQ,GAAA;EAlGhB;;;EAiHZ,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EArFsB;;;EAgGZ,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EAnFP;;;EA0Fb,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAlFU;;;EA+FjC,UAAA,IAAc,OAAA;EA3EoB;;;EAAA,UA+F/B,cAAA,CAAe,UAAA,WAAqB,OAAA;EAtEpC;;;;EAgFH,KAAA,IAAS,OAAA;EAhEpB;;;EAuEW,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EArE/B;;;EA4EW,QAAA,CACX,GAAA,EAAK,QAAA,EACL,GAAA,UACA,QAAA,QAAgB,OAAA,QACf,OAAA;EApEc;;;EAoFJ,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;EA7Ed;;;EA4FP,OAAA,CAAQ,GAAA,EAAK,QAAA,EAAU,KAAA,QAAa,OAAA;EA3DjC;;;EAkEH,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EAxDpC;;;EAqET,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;AAAA"}
|
package/esm/tagged-cache.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { parseCacheKey } from "./utils.mjs";
|
|
|
5
5
|
* Tagged Cache Wrapper
|
|
6
6
|
* Wraps a cache driver to automatically manage tag relationships
|
|
7
7
|
*/
|
|
8
|
-
var TaggedCache = class {
|
|
8
|
+
var TaggedCache = class TaggedCache {
|
|
9
9
|
/**
|
|
10
10
|
* Constructor
|
|
11
11
|
*/
|
|
@@ -38,13 +38,52 @@ var TaggedCache = class {
|
|
|
38
38
|
const indexedKey = this.indexedKey(key);
|
|
39
39
|
for (const tag of this.cacheTags) {
|
|
40
40
|
const tagKey = this.tagKey(tag);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
keys.push(indexedKey);
|
|
44
|
-
await this.driver.set(tagKey, keys, Infinity);
|
|
45
|
-
}
|
|
41
|
+
await this.addToTag(tagKey, [indexedKey]);
|
|
42
|
+
await this.pruneTag(tagKey, indexedKey);
|
|
46
43
|
}
|
|
47
44
|
}
|
|
45
|
+
static {
|
|
46
|
+
this.PRUNE_CHECKS_PER_WRITE = 20;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Drop index members whose entry no longer exists (expired, evicted, or
|
|
50
|
+
* removed outside a tagged handle). Checks a random window of at most
|
|
51
|
+
* {@link PRUNE_CHECKS_PER_WRITE} members, so a long-lived index converges
|
|
52
|
+
* to its live members without any write paying for a full scan.
|
|
53
|
+
*/
|
|
54
|
+
async pruneTag(tagKey, justWritten) {
|
|
55
|
+
const members = (await this.membersOf(tagKey)).filter((member) => member !== justWritten);
|
|
56
|
+
if (members.length === 0) return;
|
|
57
|
+
const limit = TaggedCache.PRUNE_CHECKS_PER_WRITE;
|
|
58
|
+
const start = members.length > limit ? Math.floor(Math.random() * members.length) : 0;
|
|
59
|
+
const window = Array.from({ length: Math.min(limit, members.length) }, (_, index) => members[(start + index) % members.length]);
|
|
60
|
+
const missing = [];
|
|
61
|
+
for (const member of window) if (!await this.driver.has(member)) missing.push(member);
|
|
62
|
+
if (missing.length > 0) await this.removeFromTag(tagKey, missing);
|
|
63
|
+
}
|
|
64
|
+
async addToTag(tagKey, members) {
|
|
65
|
+
if (typeof this.driver.tagAdd === "function") return this.driver.tagAdd(tagKey, members);
|
|
66
|
+
const current = await this.legacyMembers(tagKey);
|
|
67
|
+
const merged = [...new Set([...current, ...members])];
|
|
68
|
+
if (merged.length !== current.length) await this.driver.set(tagKey, merged, Infinity);
|
|
69
|
+
}
|
|
70
|
+
async membersOf(tagKey) {
|
|
71
|
+
if (typeof this.driver.tagMembers === "function") return this.driver.tagMembers(tagKey);
|
|
72
|
+
return this.legacyMembers(tagKey);
|
|
73
|
+
}
|
|
74
|
+
async removeFromTag(tagKey, members) {
|
|
75
|
+
if (typeof this.driver.tagRemove === "function") return this.driver.tagRemove(tagKey, members);
|
|
76
|
+
const dropped = new Set(members);
|
|
77
|
+
const current = await this.legacyMembers(tagKey);
|
|
78
|
+
const kept = current.filter((member) => !dropped.has(member));
|
|
79
|
+
if (kept.length === current.length) return;
|
|
80
|
+
if (kept.length === 0) await this.driver.remove(tagKey);
|
|
81
|
+
else await this.driver.set(tagKey, kept, Infinity);
|
|
82
|
+
}
|
|
83
|
+
async legacyMembers(tagKey) {
|
|
84
|
+
const value = await this.driver.get(tagKey);
|
|
85
|
+
return Array.isArray(value) ? value.filter((member) => typeof member === "string") : [];
|
|
86
|
+
}
|
|
48
87
|
/**
|
|
49
88
|
* The form a key takes inside a tag index: normalized but un-prefixed.
|
|
50
89
|
*
|
|
@@ -63,11 +102,7 @@ var TaggedCache = class {
|
|
|
63
102
|
*/
|
|
64
103
|
async getTaggedKeys() {
|
|
65
104
|
const allKeys = /* @__PURE__ */ new Set();
|
|
66
|
-
for (const tag of this.cacheTags)
|
|
67
|
-
const tagKey = this.tagKey(tag);
|
|
68
|
-
const keys = await this.driver.get(tagKey) || [];
|
|
69
|
-
for (const key of keys) allKeys.add(key);
|
|
70
|
-
}
|
|
105
|
+
for (const tag of this.cacheTags) for (const key of await this.membersOf(this.tagKey(tag))) allKeys.add(key);
|
|
71
106
|
return allKeys;
|
|
72
107
|
}
|
|
73
108
|
/**
|
|
@@ -90,19 +125,24 @@ var TaggedCache = class {
|
|
|
90
125
|
async remove(key) {
|
|
91
126
|
const indexedKey = this.indexedKey(key);
|
|
92
127
|
await this.driver.remove(key);
|
|
128
|
+
await this.detachFromTags(indexedKey);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Invalidate (clear) all keys associated with the current tags
|
|
132
|
+
*/
|
|
133
|
+
async invalidate() {
|
|
93
134
|
for (const tag of this.cacheTags) {
|
|
94
135
|
const tagKey = this.tagKey(tag);
|
|
95
|
-
const
|
|
96
|
-
await this.driver.
|
|
136
|
+
const members = await this.membersOf(tagKey);
|
|
137
|
+
for (const member of members) await this.driver.remove(member);
|
|
138
|
+
await this.removeFromTag(tagKey, members);
|
|
97
139
|
}
|
|
98
140
|
}
|
|
99
141
|
/**
|
|
100
|
-
*
|
|
142
|
+
* Remove `indexedKey` from every tag of this handle.
|
|
101
143
|
*/
|
|
102
|
-
async
|
|
103
|
-
const
|
|
104
|
-
for (const key of keysToRemove) await this.driver.remove(key);
|
|
105
|
-
for (const tag of this.cacheTags) await this.driver.remove(this.tagKey(tag));
|
|
144
|
+
async detachFromTags(indexedKey) {
|
|
145
|
+
for (const tag of this.cacheTags) await this.removeFromTag(this.tagKey(tag), [indexedKey]);
|
|
106
146
|
}
|
|
107
147
|
/**
|
|
108
148
|
* Flush all keys associated with the current tags
|
|
@@ -131,8 +171,8 @@ var TaggedCache = class {
|
|
|
131
171
|
* {@inheritdoc}
|
|
132
172
|
*/
|
|
133
173
|
async pull(key) {
|
|
134
|
-
const value = await this.
|
|
135
|
-
if (value !== null) await this.
|
|
174
|
+
const value = await this.driver.pull(key);
|
|
175
|
+
if (value !== null) await this.detachFromTags(this.indexedKey(key));
|
|
136
176
|
return value;
|
|
137
177
|
}
|
|
138
178
|
/**
|
|
@@ -145,10 +185,8 @@ var TaggedCache = class {
|
|
|
145
185
|
* {@inheritdoc}
|
|
146
186
|
*/
|
|
147
187
|
async increment(key, value = 1) {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
const newValue = current + value;
|
|
151
|
-
await this.set(key, newValue);
|
|
188
|
+
const newValue = await this.driver.increment(key, value);
|
|
189
|
+
await this.storeTaggedKey(key);
|
|
152
190
|
return newValue;
|
|
153
191
|
}
|
|
154
192
|
/**
|
package/esm/tagged-cache.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tagged-cache.mjs","names":[],"sources":["../../../../../../cache/src/tagged-cache.ts"],"sourcesContent":["import type {\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheTtl,\r\n TaggedCacheDriver,\r\n} from \"./types\";\r\nimport { parseCacheKey } from \"./utils\";\r\n\r\n/**\r\n * Tagged Cache Wrapper\r\n * Wraps a cache driver to automatically manage tag relationships\r\n */\r\nexport class TaggedCache implements TaggedCacheDriver {\r\n /**\r\n * The tags associated with this tagged cache instance\r\n */\r\n protected cacheTags: string[];\r\n\r\n /**\r\n * The underlying cache driver\r\n */\r\n protected driver: CacheDriver<any, any>;\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor(tags: string[], driver: CacheDriver<any, any>) {\r\n this.cacheTags = tags;\r\n this.driver = driver;\r\n }\r\n\r\n /**\r\n * Get the tag key prefix for storing tag-key relationships\r\n */\r\n protected tagKey(tag: string): string {\r\n return `cache:tags:${tag}`;\r\n }\r\n\r\n /**\r\n * Store tag-key relationship\r\n */\r\n protected async storeTaggedKey(key: CacheKey): Promise<void> {\r\n await this.storeTagRelationship(key);\r\n }\r\n\r\n /**\r\n * Public alias of the tag-index writer. Called by `BaseCacheDriver.applyTags`\r\n * when tags are passed inline through `CacheSetOptions.tags`.\r\n *\r\n * Takes the caller's key, never the driver-parsed one — see {@link indexedKey}.\r\n *\r\n * @internal — public for cross-class use within this package; not part of the\r\n * stable consumer API.\r\n */\r\n public async storeTagRelationship(key: CacheKey): Promise<void> {\r\n const indexedKey = this.indexedKey(key);\r\n\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n\r\n if (!keys.includes(indexedKey)) {\r\n keys.push(indexedKey);\r\n await this.driver.set(tagKey, keys, Infinity);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * The form a key takes inside a tag index: normalized but un-prefixed.\r\n *\r\n * The index is read back and deleted through `driver.remove(key)`, which\r\n * applies `globalPrefix` itself — so the index must hold the key *before*\r\n * the prefix, or invalidation would prefix it twice and delete nothing.\r\n * With a function prefix, the current prefix is re-applied at invalidation\r\n * time; the tag index key is resolved under that same prefix, so the two\r\n * agree as long as the prefix is stable for a given request/tenant.\r\n */\r\n protected indexedKey(key: CacheKey): string {\r\n return parseCacheKey(key);\r\n }\r\n\r\n /**\r\n * Get all keys associated with tags\r\n */\r\n protected async getTaggedKeys(): Promise<Set<string>> {\r\n const allKeys = new Set<string>();\r\n\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n\r\n for (const key of keys) {\r\n allKeys.add(key);\r\n }\r\n }\r\n\r\n return allKeys;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n await this.driver.set(key, value, ttlOrOptions);\r\n\r\n await this.storeTaggedKey(key);\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey): Promise<any | null> {\r\n return this.driver.get(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey): Promise<void> {\r\n const indexedKey = this.indexedKey(key);\r\n\r\n // Remove the value\r\n await this.driver.remove(key);\r\n\r\n // Remove from all tag relationships\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n const updatedKeys = keys.filter((k: string) => k !== indexedKey);\r\n await this.driver.set(tagKey, updatedKeys, Infinity);\r\n }\r\n }\r\n\r\n /**\r\n * Invalidate (clear) all keys associated with the current tags\r\n */\r\n public async invalidate(): Promise<void> {\r\n const keysToRemove = await this.getTaggedKeys();\r\n\r\n // Remove all tagged keys — indexed un-prefixed, so `remove()` prefixes once\r\n for (const key of keysToRemove) {\r\n await this.driver.remove(key);\r\n }\r\n\r\n // Clear tag relationship keys\r\n for (const tag of this.cacheTags) {\r\n await this.driver.remove(this.tagKey(tag));\r\n }\r\n }\r\n\r\n /**\r\n * Flush all keys associated with the current tags\r\n * @deprecated Use invalidate() instead for better semantics\r\n */\r\n public async flush(): Promise<void> {\r\n return this.invalidate();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n return this.driver.has(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember(\r\n key: CacheKey,\r\n ttl: number,\r\n callback: () => Promise<any>,\r\n ): Promise<any> {\r\n const value = await this.get(key);\r\n\r\n if (value !== null) {\r\n return value;\r\n }\r\n\r\n const result = await callback();\r\n await this.set(key, result, ttl);\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n const value = await this.get(key);\r\n\r\n if (value !== null) {\r\n await this.remove(key);\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n return this.set(key, value, Infinity);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\r\n const current = (await this.get(key)) || 0;\r\n\r\n if (typeof current !== \"number\") {\r\n throw new Error(\r\n `Cannot increment non-numeric value for key: ${this.driver.parseKey(key)}`,\r\n );\r\n }\r\n\r\n const newValue = current + value;\r\n await this.set(key, newValue);\r\n\r\n return newValue;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\r\n return this.increment(key, -value);\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tagged-cache.mjs","names":[],"sources":["../../../../../../cache/src/tagged-cache.ts"],"sourcesContent":["import type {\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheTtl,\r\n TaggedCacheDriver,\r\n} from \"./types\";\r\nimport { parseCacheKey } from \"./utils\";\r\n\r\n/**\r\n * Tagged Cache Wrapper\r\n * Wraps a cache driver to automatically manage tag relationships\r\n */\r\nexport class TaggedCache implements TaggedCacheDriver {\r\n /**\r\n * The tags associated with this tagged cache instance\r\n */\r\n protected cacheTags: string[];\r\n\r\n /**\r\n * The underlying cache driver\r\n */\r\n protected driver: CacheDriver<any, any>;\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor(tags: string[], driver: CacheDriver<any, any>) {\r\n this.cacheTags = tags;\r\n this.driver = driver;\r\n }\r\n\r\n /**\r\n * Get the tag key prefix for storing tag-key relationships\r\n */\r\n protected tagKey(tag: string): string {\r\n return `cache:tags:${tag}`;\r\n }\r\n\r\n /**\r\n * Store tag-key relationship\r\n */\r\n protected async storeTaggedKey(key: CacheKey): Promise<void> {\r\n await this.storeTagRelationship(key);\r\n }\r\n\r\n /**\r\n * Public alias of the tag-index writer. Called by `BaseCacheDriver.applyTags`\r\n * when tags are passed inline through `CacheSetOptions.tags`.\r\n *\r\n * Takes the caller's key, never the driver-parsed one — see {@link indexedKey}.\r\n *\r\n * @internal — public for cross-class use within this package; not part of the\r\n * stable consumer API.\r\n */\r\n public async storeTagRelationship(key: CacheKey): Promise<void> {\r\n const indexedKey = this.indexedKey(key);\r\n\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n\r\n await this.addToTag(tagKey, [indexedKey]);\r\n await this.pruneTag(tagKey, indexedKey);\r\n }\r\n }\r\n\r\n /**\r\n * Upper bound on member-existence checks per tagged write, so pruning stays\r\n * O(1) per write no matter how large an index grows.\r\n */\r\n protected static readonly PRUNE_CHECKS_PER_WRITE = 20;\r\n\r\n /**\r\n * Drop index members whose entry no longer exists (expired, evicted, or\r\n * removed outside a tagged handle). Checks a random window of at most\r\n * {@link PRUNE_CHECKS_PER_WRITE} members, so a long-lived index converges\r\n * to its live members without any write paying for a full scan.\r\n */\r\n protected async pruneTag(tagKey: string, justWritten: string): Promise<void> {\r\n const members = (await this.membersOf(tagKey)).filter((member) => member !== justWritten);\r\n\r\n if (members.length === 0) {\r\n return;\r\n }\r\n\r\n const limit = TaggedCache.PRUNE_CHECKS_PER_WRITE;\r\n const start = members.length > limit ? Math.floor(Math.random() * members.length) : 0;\r\n const window = Array.from(\r\n { length: Math.min(limit, members.length) },\r\n (_, index) => members[(start + index) % members.length]!,\n );\r\n\r\n const missing: string[] = [];\r\n\r\n for (const member of window) {\r\n if (!(await this.driver.has(member))) {\r\n missing.push(member);\r\n }\r\n }\r\n\r\n if (missing.length > 0) {\r\n await this.removeFromTag(tagKey, missing);\r\n }\r\n }\r\n\r\n // ------------------------------------------------------------\r\n // Tag-index access: the driver's primitives when it has them, else\r\n // the legacy get/set array (third-party drivers without primitives).\r\n // ------------------------------------------------------------\r\n\r\n protected async addToTag(tagKey: string, members: string[]): Promise<void> {\r\n if (typeof this.driver.tagAdd === \"function\") {\r\n return this.driver.tagAdd(tagKey, members);\r\n }\r\n\r\n const current = await this.legacyMembers(tagKey);\r\n const merged = [...new Set([...current, ...members])];\r\n\r\n if (merged.length !== current.length) {\r\n await this.driver.set(tagKey, merged, Infinity);\r\n }\r\n }\r\n\r\n protected async membersOf(tagKey: string): Promise<string[]> {\r\n if (typeof this.driver.tagMembers === \"function\") {\r\n return this.driver.tagMembers(tagKey);\r\n }\r\n\r\n return this.legacyMembers(tagKey);\r\n }\r\n\r\n protected async removeFromTag(tagKey: string, members: string[]): Promise<void> {\r\n if (typeof this.driver.tagRemove === \"function\") {\r\n return this.driver.tagRemove(tagKey, members);\r\n }\r\n\r\n const dropped = new Set(members);\r\n const current = await this.legacyMembers(tagKey);\r\n const kept = current.filter((member) => !dropped.has(member));\r\n\r\n if (kept.length === current.length) {\r\n return;\r\n }\r\n\r\n if (kept.length === 0) {\r\n await this.driver.remove(tagKey);\r\n } else {\r\n await this.driver.set(tagKey, kept, Infinity);\r\n }\r\n }\r\n\r\n protected async legacyMembers(tagKey: string): Promise<string[]> {\r\n const value = await this.driver.get(tagKey);\r\n\r\n return Array.isArray(value)\r\n ? value.filter((member): member is string => typeof member === \"string\")\r\n : [];\r\n }\r\n\r\n /**\r\n * The form a key takes inside a tag index: normalized but un-prefixed.\r\n *\r\n * The index is read back and deleted through `driver.remove(key)`, which\r\n * applies `globalPrefix` itself — so the index must hold the key *before*\r\n * the prefix, or invalidation would prefix it twice and delete nothing.\r\n * With a function prefix, the current prefix is re-applied at invalidation\r\n * time; the tag index key is resolved under that same prefix, so the two\r\n * agree as long as the prefix is stable for a given request/tenant.\r\n */\r\n protected indexedKey(key: CacheKey): string {\r\n return parseCacheKey(key);\r\n }\r\n\r\n /**\r\n * Get all keys associated with tags\r\n */\r\n protected async getTaggedKeys(): Promise<Set<string>> {\r\n const allKeys = new Set<string>();\r\n\r\n for (const tag of this.cacheTags) {\r\n for (const key of await this.membersOf(this.tagKey(tag))) {\r\n allKeys.add(key);\r\n }\r\n }\r\n\r\n return allKeys;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n await this.driver.set(key, value, ttlOrOptions);\r\n\r\n await this.storeTaggedKey(key);\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey): Promise<any | null> {\r\n return this.driver.get(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey): Promise<void> {\r\n const indexedKey = this.indexedKey(key);\r\n\r\n // Remove the value\r\n await this.driver.remove(key);\r\n\r\n // Remove from all tag relationships\r\n await this.detachFromTags(indexedKey);\r\n }\r\n\r\n /**\r\n * Invalidate (clear) all keys associated with the current tags\r\n */\r\n public async invalidate(): Promise<void> {\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const members = await this.membersOf(tagKey);\r\n\r\n // Remove the tagged entries — indexed un-prefixed, so `remove()` prefixes once\r\n for (const member of members) {\r\n await this.driver.remove(member);\r\n }\r\n\r\n // Remove exactly the members read above rather than dropping the whole\r\n // index: a key tagged concurrently (after the read) keeps its entry in\r\n // the index, so a later invalidate still reaches it.\r\n await this.removeFromTag(tagKey, members);\r\n }\r\n }\r\n\r\n /**\r\n * Remove `indexedKey` from every tag of this handle.\r\n */\r\n protected async detachFromTags(indexedKey: string): Promise<void> {\r\n for (const tag of this.cacheTags) {\r\n await this.removeFromTag(this.tagKey(tag), [indexedKey]);\r\n }\r\n }\r\n\r\n /**\r\n * Flush all keys associated with the current tags\r\n * @deprecated Use invalidate() instead for better semantics\r\n */\r\n public async flush(): Promise<void> {\r\n return this.invalidate();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n return this.driver.has(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember(\r\n key: CacheKey,\r\n ttl: number,\r\n callback: () => Promise<any>,\r\n ): Promise<any> {\r\n const value = await this.get(key);\r\n\r\n if (value !== null) {\r\n return value;\r\n }\r\n\r\n const result = await callback();\r\n await this.set(key, result, ttl);\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n // The driver's own `pull` (atomic on redis / pg / in-process drivers)\r\n // hands the value out once; then the key leaves this handle's tags.\r\n const value = await this.driver.pull(key);\r\n\r\n if (value !== null) {\r\n await this.detachFromTags(this.indexedKey(key));\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n return this.set(key, value, Infinity);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\r\n // The driver's atomic increment (INCRBY / pg upsert / synchronous\r\n // in-process), then record the tag relationship.\r\n const newValue = await this.driver.increment(key, value);\r\n\r\n await this.storeTaggedKey(key);\r\n\r\n return newValue;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\r\n return this.increment(key, -value);\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -61,14 +61,18 @@ declare class TaggedScopedCache implements TaggedScopedCacheContract {
|
|
|
61
61
|
*/
|
|
62
62
|
has(key: CacheKey): Promise<boolean>;
|
|
63
63
|
/**
|
|
64
|
-
* Remove the scoped key
|
|
65
|
-
*
|
|
64
|
+
* Remove the scoped key and take it out of every tag index this handle
|
|
65
|
+
* writes to (scope tags + handle tags).
|
|
66
66
|
*/
|
|
67
67
|
remove(key: CacheKey): Promise<void>;
|
|
68
68
|
/**
|
|
69
|
-
* Read-and-remove the scoped key.
|
|
69
|
+
* Read-and-remove the scoped key, then take it out of this handle's tags.
|
|
70
70
|
*/
|
|
71
71
|
pull<T = any>(key: CacheKey): Promise<T | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Remove the un-prefixed scoped key from every tag index of this handle.
|
|
74
|
+
*/
|
|
75
|
+
protected detachFromTags(key: CacheKey): Promise<void>;
|
|
72
76
|
/**
|
|
73
77
|
* Permanent write with handle tags applied. Bypasses both the scope's and
|
|
74
78
|
* the caller's TTL (forever means forever) — only tags get injected.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tagged-scoped-cache.d.mts","names":[],"sources":["../../../../../../cache/src/tagged-scoped-cache.ts"],"mappings":";;;;;;AA4CA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,iBAAA,YAA6B,yBAAA;
|
|
1
|
+
{"version":3,"file":"tagged-scoped-cache.d.mts","names":[],"sources":["../../../../../../cache/src/tagged-scoped-cache.ts"],"mappings":";;;;;;AA4CA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,iBAAA,YAA6B,yBAAA;EAwIjC;;;;;EAAA,mBAlIY,KAAA,EAAO,WAAA;EAqIvB;;;;EAAA,mBA/HgB,UAAA;EA4JQ;;;;cAtJR,KAAA,EAAO,WAAA,EAAa,UAAA;EAlBC;;;;EA2BjC,GAAA,CACL,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAbuB;;;EAsBnB,GAAA,UAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAZrC;;;EAmBA,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EAjBC;;;;EAyBf,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAfX;;;EAuBZ,IAAA,UAAc,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAhB5C;;;EAAA,UA6BS,cAAA,CAAe,GAAA,EAAK,QAAA,GAAW,OAAA;EArBlC;;;;EAwCN,OAAA,UAAiB,GAAA,EAAK,QAAA,EAAU,KAAA,EAAO,CAAA,GAAI,OAAA,CAAQ,CAAA;EAhCxC;;;;;EA4CL,KAAA,CAAM,GAAA,EAAK,QAAA,EAAU,KAAA,OAAY,GAAA,YAAe,OAAA;EA/BzB;;;EA2D7B,QAAA,UACL,GAAA,EAAK,QAAA,EACL,YAAA,EAAc,QAAA,GAAW,eAAA,EACzB,QAAA,QAAgB,OAAA,CAAQ,CAAA,IACvB,OAAA,CAAQ,CAAA;EA5CI;;;;EAyDR,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAiB,OAAA;EAzDC;;;EAgE3C,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAiB,OAAA;EApD9B;;;;;EA6DN,UAAA,IAAc,OAAA;EAhCpB;;;;;EAAA,UA+CG,cAAA,CAAe,GAAA,EAAK,QAAA;AAAA"}
|