@philiprehberger/cache-kit 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/dist/index.cjs +37 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +37 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @philiprehberger/cache-kit
|
|
2
2
|
|
|
3
|
+
[](https://github.com/philiprehberger/cache-kit/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@philiprehberger/cache-kit)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
In-memory LRU cache with TTL, stale-while-revalidate, and tag invalidation.
|
|
4
8
|
|
|
5
9
|
## Installation
|
|
@@ -67,9 +71,21 @@ const same = await getUser('123'); // served from cache
|
|
|
67
71
|
const getUser = cache.wrap(
|
|
68
72
|
'user',
|
|
69
73
|
(id: string) => db.users.findById(id),
|
|
70
|
-
{
|
|
74
|
+
{
|
|
75
|
+
ttl: '5m',
|
|
76
|
+
staleWhileRevalidate: '1m',
|
|
77
|
+
onRevalidateError: (err, key) => console.error(`SWR failed for ${key}:`, err),
|
|
78
|
+
},
|
|
71
79
|
);
|
|
72
80
|
// Serves stale data immediately while refreshing in the background
|
|
81
|
+
// If revalidation fails, onRevalidateError is called instead of silently swallowing the error
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Keys & Size
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
cache.keys(); // ['user:1', 'user:2'] — all non-expired keys (lazy-cleans expired entries)
|
|
88
|
+
cache.size(); // 2 — number of entries in the store
|
|
73
89
|
```
|
|
74
90
|
|
|
75
91
|
### Stats
|
|
@@ -100,6 +116,8 @@ cache.load(data); // restore from JSON
|
|
|
100
116
|
| `invalidateTag` | `(tag: string) => number` | Remove all entries with the given tag. Returns count removed. |
|
|
101
117
|
| `wrap` | `(keyPrefix, fn, opts?) => (...args) => Promise` | Memoize an async function with cache. |
|
|
102
118
|
| `stats` | `() => CacheStats` | Get hit/miss/size statistics. |
|
|
119
|
+
| `keys` | `() => string[]` | Return all non-expired keys. Lazy-cleans expired entries. |
|
|
120
|
+
| `size` | `() => number` | Return the number of entries in the store. |
|
|
103
121
|
| `dump` | `() => SerializedCache` | Serialize cache contents for persistence. |
|
|
104
122
|
| `load` | `(data: SerializedCache) => void` | Restore cache from serialized data. |
|
|
105
123
|
|
|
@@ -118,6 +136,14 @@ cache.load(data); // restore from JSON
|
|
|
118
136
|
| `tags` | `string[]` | Tags for group invalidation. |
|
|
119
137
|
| `staleWhileRevalidate` | `string \| number` | Window before expiry where stale data is served while refreshing. |
|
|
120
138
|
|
|
139
|
+
### `WrapOptions`
|
|
140
|
+
|
|
141
|
+
Extends `SetOptions` with:
|
|
142
|
+
|
|
143
|
+
| Property | Type | Description |
|
|
144
|
+
|----------|------|-------------|
|
|
145
|
+
| `onRevalidateError` | `(error: Error, key: string) => void` | Called when a stale-while-revalidate background refresh fails. If omitted, errors are silently swallowed. |
|
|
146
|
+
|
|
121
147
|
### Duration Strings
|
|
122
148
|
|
|
123
149
|
`"100ms"`, `"30s"`, `"5m"`, `"1h"`, `"1d"` — or pass milliseconds as a number.
|
package/dist/index.cjs
CHANGED
|
@@ -69,10 +69,10 @@ function createCache(options = {}) {
|
|
|
69
69
|
const entry = store.get(key);
|
|
70
70
|
if (entry) {
|
|
71
71
|
for (const tag of entry.tags) {
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
if (
|
|
72
|
+
const keys2 = tagIndex.get(tag);
|
|
73
|
+
if (keys2) {
|
|
74
|
+
keys2.delete(key);
|
|
75
|
+
if (keys2.size === 0) tagIndex.delete(tag);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
store.delete(key);
|
|
@@ -101,12 +101,12 @@ function createCache(options = {}) {
|
|
|
101
101
|
};
|
|
102
102
|
store.set(key, entry);
|
|
103
103
|
for (const tag of tags) {
|
|
104
|
-
let
|
|
105
|
-
if (!
|
|
106
|
-
|
|
107
|
-
tagIndex.set(tag,
|
|
104
|
+
let keys2 = tagIndex.get(tag);
|
|
105
|
+
if (!keys2) {
|
|
106
|
+
keys2 = /* @__PURE__ */ new Set();
|
|
107
|
+
tagIndex.set(tag, keys2);
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
keys2.add(key);
|
|
110
110
|
}
|
|
111
111
|
evictLRU();
|
|
112
112
|
}
|
|
@@ -147,10 +147,10 @@ function createCache(options = {}) {
|
|
|
147
147
|
misses = 0;
|
|
148
148
|
}
|
|
149
149
|
function invalidateTag(tag) {
|
|
150
|
-
const
|
|
151
|
-
if (!
|
|
152
|
-
const count =
|
|
153
|
-
for (const key of [...
|
|
150
|
+
const keys2 = tagIndex.get(tag);
|
|
151
|
+
if (!keys2) return 0;
|
|
152
|
+
const count = keys2.size;
|
|
153
|
+
for (const key of [...keys2]) {
|
|
154
154
|
deleteEntry(key);
|
|
155
155
|
}
|
|
156
156
|
return count;
|
|
@@ -166,7 +166,10 @@ function createCache(options = {}) {
|
|
|
166
166
|
store.set(cacheKey, entry);
|
|
167
167
|
if (isStale(entry) && !revalidating.has(cacheKey)) {
|
|
168
168
|
revalidating.add(cacheKey);
|
|
169
|
-
fn(...args).then((value2) => set(cacheKey, value2, opts)).catch(() => {
|
|
169
|
+
fn(...args).then((value2) => set(cacheKey, value2, opts)).catch((err) => {
|
|
170
|
+
if (opts == null ? void 0 : opts.onRevalidateError) {
|
|
171
|
+
opts.onRevalidateError(err instanceof Error ? err : new Error(String(err)), cacheKey);
|
|
172
|
+
}
|
|
170
173
|
}).finally(() => revalidating.delete(cacheKey));
|
|
171
174
|
}
|
|
172
175
|
return entry.value;
|
|
@@ -199,16 +202,30 @@ function createCache(options = {}) {
|
|
|
199
202
|
const _a = entry, { key: _key } = _a, rest = __objRest(_a, ["key"]);
|
|
200
203
|
store.set(key, rest);
|
|
201
204
|
for (const tag of rest.tags) {
|
|
202
|
-
let
|
|
203
|
-
if (!
|
|
204
|
-
|
|
205
|
-
tagIndex.set(tag,
|
|
205
|
+
let keys2 = tagIndex.get(tag);
|
|
206
|
+
if (!keys2) {
|
|
207
|
+
keys2 = /* @__PURE__ */ new Set();
|
|
208
|
+
tagIndex.set(tag, keys2);
|
|
206
209
|
}
|
|
207
|
-
|
|
210
|
+
keys2.add(key);
|
|
208
211
|
}
|
|
209
212
|
}
|
|
210
213
|
}
|
|
211
|
-
|
|
214
|
+
function keys() {
|
|
215
|
+
const result = [];
|
|
216
|
+
for (const [key, entry] of store) {
|
|
217
|
+
if (isExpired(entry)) {
|
|
218
|
+
deleteEntry(key);
|
|
219
|
+
} else {
|
|
220
|
+
result.push(key);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
function size() {
|
|
226
|
+
return store.size;
|
|
227
|
+
}
|
|
228
|
+
return { set, get, has, delete: del, clear, invalidateTag, wrap, stats, dump, load, keys, size };
|
|
212
229
|
}
|
|
213
230
|
|
|
214
231
|
exports.createCache = createCache;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/duration.ts","../src/cache.ts"],"names":["value"],"mappings":"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","file":"index.cjs","sourcesContent":["const UNITS: Record<string, number> = {\n ms: 1,\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n};\n\nconst DURATION_RE = /^(\\d+)(ms|s|m|h|d)$/;\n\nexport function parseDuration(value: string | number): number {\n if (typeof value === 'number') return value;\n\n const match = value.match(DURATION_RE);\n if (!match) {\n throw new Error(`Invalid duration: \"${value}\". Use format like \"5m\", \"1h\", \"30s\", \"100ms\", \"1d\"`);\n }\n\n return parseInt(match[1], 10) * UNITS[match[2]];\n}\n","import type { CacheOptions, SetOptions, WrapOptions, CacheStats, CacheEntry, SerializedCache } from './types.js';\nimport { parseDuration } from './duration.js';\n\nexport function createCache<V = unknown>(options: CacheOptions = {}) {\n const { maxItems = Infinity } = options;\n const defaultTTL = options.defaultTTL ? parseDuration(options.defaultTTL) : null;\n\n const store = new Map<string, CacheEntry<V>>();\n const tagIndex = new Map<string, Set<string>>();\n let hits = 0;\n let misses = 0;\n\n function evictLRU(): void {\n if (store.size <= maxItems) return;\n const firstKey = store.keys().next().value;\n if (firstKey !== undefined) {\n deleteEntry(firstKey);\n }\n }\n\n function deleteEntry(key: string): void {\n const entry = store.get(key);\n if (entry) {\n for (const tag of entry.tags) {\n const keys = tagIndex.get(tag);\n if (keys) {\n keys.delete(key);\n if (keys.size === 0) tagIndex.delete(tag);\n }\n }\n store.delete(key);\n }\n }\n\n function isExpired(entry: CacheEntry<V>): boolean {\n if (entry.expiresAt === null) return false;\n return Date.now() > entry.expiresAt;\n }\n\n function isStale(entry: CacheEntry<V>): boolean {\n if (entry.staleAt === null) return false;\n return Date.now() > entry.staleAt;\n }\n\n function set(key: string, value: V, opts?: SetOptions): void {\n deleteEntry(key);\n\n const ttl = opts?.ttl ? parseDuration(opts.ttl) : defaultTTL;\n const swr = opts?.staleWhileRevalidate ? parseDuration(opts.staleWhileRevalidate) : null;\n const now = Date.now();\n const tags = opts?.tags ?? [];\n\n const entry: CacheEntry<V> = {\n value,\n expiresAt: ttl ? now + ttl : null,\n staleAt: ttl && swr ? now + ttl - swr : null,\n tags,\n };\n\n store.set(key, entry);\n\n for (const tag of tags) {\n let keys = tagIndex.get(tag);\n if (!keys) {\n keys = new Set();\n tagIndex.set(tag, keys);\n }\n keys.add(key);\n }\n\n evictLRU();\n }\n\n function get(key: string): V | undefined {\n const entry = store.get(key);\n if (!entry) {\n misses++;\n return undefined;\n }\n\n if (isExpired(entry)) {\n deleteEntry(key);\n misses++;\n return undefined;\n }\n\n hits++;\n // Move to end for LRU\n store.delete(key);\n store.set(key, entry);\n\n return entry.value;\n }\n\n function has(key: string): boolean {\n const entry = store.get(key);\n if (!entry) return false;\n if (isExpired(entry)) {\n deleteEntry(key);\n return false;\n }\n return true;\n }\n\n function del(key: string): boolean {\n if (!store.has(key)) return false;\n deleteEntry(key);\n return true;\n }\n\n function clear(): void {\n store.clear();\n tagIndex.clear();\n hits = 0;\n misses = 0;\n }\n\n function invalidateTag(tag: string): number {\n const keys = tagIndex.get(tag);\n if (!keys) return 0;\n const count = keys.size;\n for (const key of [...keys]) {\n deleteEntry(key);\n }\n return count;\n }\n\n function wrap<TArgs extends unknown[], TReturn extends V>(\n keyPrefix: string,\n fn: (...args: TArgs) => Promise<TReturn>,\n opts?: WrapOptions,\n ): (...args: TArgs) => Promise<TReturn> {\n const revalidating = new Set<string>();\n\n return async (...args: TArgs): Promise<TReturn> => {\n const cacheKey = `${keyPrefix}:${JSON.stringify(args)}`;\n const entry = store.get(cacheKey);\n\n if (entry && !isExpired(entry)) {\n hits++;\n // Move to end for LRU\n store.delete(cacheKey);\n store.set(cacheKey, entry);\n\n // Stale-while-revalidate\n if (isStale(entry) && !revalidating.has(cacheKey)) {\n revalidating.add(cacheKey);\n fn(...args)\n .then((value) => set(cacheKey, value, opts))\n .catch(() => {})\n .finally(() => revalidating.delete(cacheKey));\n }\n\n return entry.value as TReturn;\n }\n\n misses++;\n const value = await fn(...args);\n set(cacheKey, value, opts);\n return value;\n };\n }\n\n function stats(): CacheStats {\n const total = hits + misses;\n return {\n hits,\n misses,\n hitRate: total === 0 ? 0 : hits / total,\n size: store.size,\n };\n }\n\n function dump(): SerializedCache {\n const entries: SerializedCache['entries'] = [];\n for (const [key, entry] of store) {\n entries.push([key, { ...entry, key }]);\n }\n return { entries };\n }\n\n function load(data: SerializedCache): void {\n clear();\n for (const [key, entry] of data.entries) {\n const { key: _key, ...rest } = entry;\n store.set(key, rest as CacheEntry<V>);\n for (const tag of rest.tags) {\n let keys = tagIndex.get(tag);\n if (!keys) {\n keys = new Set();\n tagIndex.set(tag, keys);\n }\n keys.add(key);\n }\n }\n }\n\n return { set, get, has, delete: del, clear, invalidateTag, wrap, stats, dump, load };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/duration.ts","../src/cache.ts"],"names":["keys","value"],"mappings":"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","file":"index.cjs","sourcesContent":["const UNITS: Record<string, number> = {\n ms: 1,\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n};\n\nconst DURATION_RE = /^(\\d+)(ms|s|m|h|d)$/;\n\nexport function parseDuration(value: string | number): number {\n if (typeof value === 'number') return value;\n\n const match = value.match(DURATION_RE);\n if (!match) {\n throw new Error(`Invalid duration: \"${value}\". Use format like \"5m\", \"1h\", \"30s\", \"100ms\", \"1d\"`);\n }\n\n return parseInt(match[1], 10) * UNITS[match[2]];\n}\n","import type { CacheOptions, SetOptions, WrapOptions, CacheStats, CacheEntry, SerializedCache } from './types.js';\nimport { parseDuration } from './duration.js';\n\nexport function createCache<V = unknown>(options: CacheOptions = {}) {\n const { maxItems = Infinity } = options;\n const defaultTTL = options.defaultTTL ? parseDuration(options.defaultTTL) : null;\n\n const store = new Map<string, CacheEntry<V>>();\n const tagIndex = new Map<string, Set<string>>();\n let hits = 0;\n let misses = 0;\n\n function evictLRU(): void {\n if (store.size <= maxItems) return;\n const firstKey = store.keys().next().value;\n if (firstKey !== undefined) {\n deleteEntry(firstKey);\n }\n }\n\n function deleteEntry(key: string): void {\n const entry = store.get(key);\n if (entry) {\n for (const tag of entry.tags) {\n const keys = tagIndex.get(tag);\n if (keys) {\n keys.delete(key);\n if (keys.size === 0) tagIndex.delete(tag);\n }\n }\n store.delete(key);\n }\n }\n\n function isExpired(entry: CacheEntry<V>): boolean {\n if (entry.expiresAt === null) return false;\n return Date.now() > entry.expiresAt;\n }\n\n function isStale(entry: CacheEntry<V>): boolean {\n if (entry.staleAt === null) return false;\n return Date.now() > entry.staleAt;\n }\n\n function set(key: string, value: V, opts?: SetOptions): void {\n deleteEntry(key);\n\n const ttl = opts?.ttl ? parseDuration(opts.ttl) : defaultTTL;\n const swr = opts?.staleWhileRevalidate ? parseDuration(opts.staleWhileRevalidate) : null;\n const now = Date.now();\n const tags = opts?.tags ?? [];\n\n const entry: CacheEntry<V> = {\n value,\n expiresAt: ttl ? now + ttl : null,\n staleAt: ttl && swr ? now + ttl - swr : null,\n tags,\n };\n\n store.set(key, entry);\n\n for (const tag of tags) {\n let keys = tagIndex.get(tag);\n if (!keys) {\n keys = new Set();\n tagIndex.set(tag, keys);\n }\n keys.add(key);\n }\n\n evictLRU();\n }\n\n function get(key: string): V | undefined {\n const entry = store.get(key);\n if (!entry) {\n misses++;\n return undefined;\n }\n\n if (isExpired(entry)) {\n deleteEntry(key);\n misses++;\n return undefined;\n }\n\n hits++;\n // Move to end for LRU\n store.delete(key);\n store.set(key, entry);\n\n return entry.value;\n }\n\n function has(key: string): boolean {\n const entry = store.get(key);\n if (!entry) return false;\n if (isExpired(entry)) {\n deleteEntry(key);\n return false;\n }\n return true;\n }\n\n function del(key: string): boolean {\n if (!store.has(key)) return false;\n deleteEntry(key);\n return true;\n }\n\n function clear(): void {\n store.clear();\n tagIndex.clear();\n hits = 0;\n misses = 0;\n }\n\n function invalidateTag(tag: string): number {\n const keys = tagIndex.get(tag);\n if (!keys) return 0;\n const count = keys.size;\n for (const key of [...keys]) {\n deleteEntry(key);\n }\n return count;\n }\n\n function wrap<TArgs extends unknown[], TReturn extends V>(\n keyPrefix: string,\n fn: (...args: TArgs) => Promise<TReturn>,\n opts?: WrapOptions,\n ): (...args: TArgs) => Promise<TReturn> {\n const revalidating = new Set<string>();\n\n return async (...args: TArgs): Promise<TReturn> => {\n const cacheKey = `${keyPrefix}:${JSON.stringify(args)}`;\n const entry = store.get(cacheKey);\n\n if (entry && !isExpired(entry)) {\n hits++;\n // Move to end for LRU\n store.delete(cacheKey);\n store.set(cacheKey, entry);\n\n // Stale-while-revalidate\n if (isStale(entry) && !revalidating.has(cacheKey)) {\n revalidating.add(cacheKey);\n fn(...args)\n .then((value) => set(cacheKey, value, opts))\n .catch((err: unknown) => {\n if (opts?.onRevalidateError) {\n opts.onRevalidateError(err instanceof Error ? err : new Error(String(err)), cacheKey);\n }\n })\n .finally(() => revalidating.delete(cacheKey));\n }\n\n return entry.value as TReturn;\n }\n\n misses++;\n const value = await fn(...args);\n set(cacheKey, value, opts);\n return value;\n };\n }\n\n function stats(): CacheStats {\n const total = hits + misses;\n return {\n hits,\n misses,\n hitRate: total === 0 ? 0 : hits / total,\n size: store.size,\n };\n }\n\n function dump(): SerializedCache {\n const entries: SerializedCache['entries'] = [];\n for (const [key, entry] of store) {\n entries.push([key, { ...entry, key }]);\n }\n return { entries };\n }\n\n function load(data: SerializedCache): void {\n clear();\n for (const [key, entry] of data.entries) {\n const { key: _key, ...rest } = entry;\n store.set(key, rest as CacheEntry<V>);\n for (const tag of rest.tags) {\n let keys = tagIndex.get(tag);\n if (!keys) {\n keys = new Set();\n tagIndex.set(tag, keys);\n }\n keys.add(key);\n }\n }\n }\n\n function keys(): string[] {\n const result: string[] = [];\n for (const [key, entry] of store) {\n if (isExpired(entry)) {\n deleteEntry(key);\n } else {\n result.push(key);\n }\n }\n return result;\n }\n\n function size(): number {\n return store.size;\n }\n\n return { set, get, has, delete: del, clear, invalidateTag, wrap, stats, dump, load, keys, size };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -11,6 +11,7 @@ interface WrapOptions {
|
|
|
11
11
|
ttl?: string | number;
|
|
12
12
|
staleWhileRevalidate?: string | number;
|
|
13
13
|
tags?: string[];
|
|
14
|
+
onRevalidateError?: (error: Error, key: string) => void;
|
|
14
15
|
}
|
|
15
16
|
interface CacheStats {
|
|
16
17
|
hits: number;
|
|
@@ -41,6 +42,8 @@ declare function createCache<V = unknown>(options?: CacheOptions): {
|
|
|
41
42
|
stats: () => CacheStats;
|
|
42
43
|
dump: () => SerializedCache;
|
|
43
44
|
load: (data: SerializedCache) => void;
|
|
45
|
+
keys: () => string[];
|
|
46
|
+
size: () => number;
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
declare function parseDuration(value: string | number): number;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ interface WrapOptions {
|
|
|
11
11
|
ttl?: string | number;
|
|
12
12
|
staleWhileRevalidate?: string | number;
|
|
13
13
|
tags?: string[];
|
|
14
|
+
onRevalidateError?: (error: Error, key: string) => void;
|
|
14
15
|
}
|
|
15
16
|
interface CacheStats {
|
|
16
17
|
hits: number;
|
|
@@ -41,6 +42,8 @@ declare function createCache<V = unknown>(options?: CacheOptions): {
|
|
|
41
42
|
stats: () => CacheStats;
|
|
42
43
|
dump: () => SerializedCache;
|
|
43
44
|
load: (data: SerializedCache) => void;
|
|
45
|
+
keys: () => string[];
|
|
46
|
+
size: () => number;
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
declare function parseDuration(value: string | number): number;
|
package/dist/index.js
CHANGED
|
@@ -67,10 +67,10 @@ function createCache(options = {}) {
|
|
|
67
67
|
const entry = store.get(key);
|
|
68
68
|
if (entry) {
|
|
69
69
|
for (const tag of entry.tags) {
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
if (
|
|
70
|
+
const keys2 = tagIndex.get(tag);
|
|
71
|
+
if (keys2) {
|
|
72
|
+
keys2.delete(key);
|
|
73
|
+
if (keys2.size === 0) tagIndex.delete(tag);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
store.delete(key);
|
|
@@ -99,12 +99,12 @@ function createCache(options = {}) {
|
|
|
99
99
|
};
|
|
100
100
|
store.set(key, entry);
|
|
101
101
|
for (const tag of tags) {
|
|
102
|
-
let
|
|
103
|
-
if (!
|
|
104
|
-
|
|
105
|
-
tagIndex.set(tag,
|
|
102
|
+
let keys2 = tagIndex.get(tag);
|
|
103
|
+
if (!keys2) {
|
|
104
|
+
keys2 = /* @__PURE__ */ new Set();
|
|
105
|
+
tagIndex.set(tag, keys2);
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
keys2.add(key);
|
|
108
108
|
}
|
|
109
109
|
evictLRU();
|
|
110
110
|
}
|
|
@@ -145,10 +145,10 @@ function createCache(options = {}) {
|
|
|
145
145
|
misses = 0;
|
|
146
146
|
}
|
|
147
147
|
function invalidateTag(tag) {
|
|
148
|
-
const
|
|
149
|
-
if (!
|
|
150
|
-
const count =
|
|
151
|
-
for (const key of [...
|
|
148
|
+
const keys2 = tagIndex.get(tag);
|
|
149
|
+
if (!keys2) return 0;
|
|
150
|
+
const count = keys2.size;
|
|
151
|
+
for (const key of [...keys2]) {
|
|
152
152
|
deleteEntry(key);
|
|
153
153
|
}
|
|
154
154
|
return count;
|
|
@@ -164,7 +164,10 @@ function createCache(options = {}) {
|
|
|
164
164
|
store.set(cacheKey, entry);
|
|
165
165
|
if (isStale(entry) && !revalidating.has(cacheKey)) {
|
|
166
166
|
revalidating.add(cacheKey);
|
|
167
|
-
fn(...args).then((value2) => set(cacheKey, value2, opts)).catch(() => {
|
|
167
|
+
fn(...args).then((value2) => set(cacheKey, value2, opts)).catch((err) => {
|
|
168
|
+
if (opts == null ? void 0 : opts.onRevalidateError) {
|
|
169
|
+
opts.onRevalidateError(err instanceof Error ? err : new Error(String(err)), cacheKey);
|
|
170
|
+
}
|
|
168
171
|
}).finally(() => revalidating.delete(cacheKey));
|
|
169
172
|
}
|
|
170
173
|
return entry.value;
|
|
@@ -197,16 +200,30 @@ function createCache(options = {}) {
|
|
|
197
200
|
const _a = entry, { key: _key } = _a, rest = __objRest(_a, ["key"]);
|
|
198
201
|
store.set(key, rest);
|
|
199
202
|
for (const tag of rest.tags) {
|
|
200
|
-
let
|
|
201
|
-
if (!
|
|
202
|
-
|
|
203
|
-
tagIndex.set(tag,
|
|
203
|
+
let keys2 = tagIndex.get(tag);
|
|
204
|
+
if (!keys2) {
|
|
205
|
+
keys2 = /* @__PURE__ */ new Set();
|
|
206
|
+
tagIndex.set(tag, keys2);
|
|
204
207
|
}
|
|
205
|
-
|
|
208
|
+
keys2.add(key);
|
|
206
209
|
}
|
|
207
210
|
}
|
|
208
211
|
}
|
|
209
|
-
|
|
212
|
+
function keys() {
|
|
213
|
+
const result = [];
|
|
214
|
+
for (const [key, entry] of store) {
|
|
215
|
+
if (isExpired(entry)) {
|
|
216
|
+
deleteEntry(key);
|
|
217
|
+
} else {
|
|
218
|
+
result.push(key);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
function size() {
|
|
224
|
+
return store.size;
|
|
225
|
+
}
|
|
226
|
+
return { set, get, has, delete: del, clear, invalidateTag, wrap, stats, dump, load, keys, size };
|
|
210
227
|
}
|
|
211
228
|
|
|
212
229
|
export { createCache, parseDuration };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/duration.ts","../src/cache.ts"],"names":["value"],"mappings":"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","file":"index.js","sourcesContent":["const UNITS: Record<string, number> = {\n ms: 1,\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n};\n\nconst DURATION_RE = /^(\\d+)(ms|s|m|h|d)$/;\n\nexport function parseDuration(value: string | number): number {\n if (typeof value === 'number') return value;\n\n const match = value.match(DURATION_RE);\n if (!match) {\n throw new Error(`Invalid duration: \"${value}\". Use format like \"5m\", \"1h\", \"30s\", \"100ms\", \"1d\"`);\n }\n\n return parseInt(match[1], 10) * UNITS[match[2]];\n}\n","import type { CacheOptions, SetOptions, WrapOptions, CacheStats, CacheEntry, SerializedCache } from './types.js';\nimport { parseDuration } from './duration.js';\n\nexport function createCache<V = unknown>(options: CacheOptions = {}) {\n const { maxItems = Infinity } = options;\n const defaultTTL = options.defaultTTL ? parseDuration(options.defaultTTL) : null;\n\n const store = new Map<string, CacheEntry<V>>();\n const tagIndex = new Map<string, Set<string>>();\n let hits = 0;\n let misses = 0;\n\n function evictLRU(): void {\n if (store.size <= maxItems) return;\n const firstKey = store.keys().next().value;\n if (firstKey !== undefined) {\n deleteEntry(firstKey);\n }\n }\n\n function deleteEntry(key: string): void {\n const entry = store.get(key);\n if (entry) {\n for (const tag of entry.tags) {\n const keys = tagIndex.get(tag);\n if (keys) {\n keys.delete(key);\n if (keys.size === 0) tagIndex.delete(tag);\n }\n }\n store.delete(key);\n }\n }\n\n function isExpired(entry: CacheEntry<V>): boolean {\n if (entry.expiresAt === null) return false;\n return Date.now() > entry.expiresAt;\n }\n\n function isStale(entry: CacheEntry<V>): boolean {\n if (entry.staleAt === null) return false;\n return Date.now() > entry.staleAt;\n }\n\n function set(key: string, value: V, opts?: SetOptions): void {\n deleteEntry(key);\n\n const ttl = opts?.ttl ? parseDuration(opts.ttl) : defaultTTL;\n const swr = opts?.staleWhileRevalidate ? parseDuration(opts.staleWhileRevalidate) : null;\n const now = Date.now();\n const tags = opts?.tags ?? [];\n\n const entry: CacheEntry<V> = {\n value,\n expiresAt: ttl ? now + ttl : null,\n staleAt: ttl && swr ? now + ttl - swr : null,\n tags,\n };\n\n store.set(key, entry);\n\n for (const tag of tags) {\n let keys = tagIndex.get(tag);\n if (!keys) {\n keys = new Set();\n tagIndex.set(tag, keys);\n }\n keys.add(key);\n }\n\n evictLRU();\n }\n\n function get(key: string): V | undefined {\n const entry = store.get(key);\n if (!entry) {\n misses++;\n return undefined;\n }\n\n if (isExpired(entry)) {\n deleteEntry(key);\n misses++;\n return undefined;\n }\n\n hits++;\n // Move to end for LRU\n store.delete(key);\n store.set(key, entry);\n\n return entry.value;\n }\n\n function has(key: string): boolean {\n const entry = store.get(key);\n if (!entry) return false;\n if (isExpired(entry)) {\n deleteEntry(key);\n return false;\n }\n return true;\n }\n\n function del(key: string): boolean {\n if (!store.has(key)) return false;\n deleteEntry(key);\n return true;\n }\n\n function clear(): void {\n store.clear();\n tagIndex.clear();\n hits = 0;\n misses = 0;\n }\n\n function invalidateTag(tag: string): number {\n const keys = tagIndex.get(tag);\n if (!keys) return 0;\n const count = keys.size;\n for (const key of [...keys]) {\n deleteEntry(key);\n }\n return count;\n }\n\n function wrap<TArgs extends unknown[], TReturn extends V>(\n keyPrefix: string,\n fn: (...args: TArgs) => Promise<TReturn>,\n opts?: WrapOptions,\n ): (...args: TArgs) => Promise<TReturn> {\n const revalidating = new Set<string>();\n\n return async (...args: TArgs): Promise<TReturn> => {\n const cacheKey = `${keyPrefix}:${JSON.stringify(args)}`;\n const entry = store.get(cacheKey);\n\n if (entry && !isExpired(entry)) {\n hits++;\n // Move to end for LRU\n store.delete(cacheKey);\n store.set(cacheKey, entry);\n\n // Stale-while-revalidate\n if (isStale(entry) && !revalidating.has(cacheKey)) {\n revalidating.add(cacheKey);\n fn(...args)\n .then((value) => set(cacheKey, value, opts))\n .catch(() => {})\n .finally(() => revalidating.delete(cacheKey));\n }\n\n return entry.value as TReturn;\n }\n\n misses++;\n const value = await fn(...args);\n set(cacheKey, value, opts);\n return value;\n };\n }\n\n function stats(): CacheStats {\n const total = hits + misses;\n return {\n hits,\n misses,\n hitRate: total === 0 ? 0 : hits / total,\n size: store.size,\n };\n }\n\n function dump(): SerializedCache {\n const entries: SerializedCache['entries'] = [];\n for (const [key, entry] of store) {\n entries.push([key, { ...entry, key }]);\n }\n return { entries };\n }\n\n function load(data: SerializedCache): void {\n clear();\n for (const [key, entry] of data.entries) {\n const { key: _key, ...rest } = entry;\n store.set(key, rest as CacheEntry<V>);\n for (const tag of rest.tags) {\n let keys = tagIndex.get(tag);\n if (!keys) {\n keys = new Set();\n tagIndex.set(tag, keys);\n }\n keys.add(key);\n }\n }\n }\n\n return { set, get, has, delete: del, clear, invalidateTag, wrap, stats, dump, load };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/duration.ts","../src/cache.ts"],"names":["keys","value"],"mappings":"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","file":"index.js","sourcesContent":["const UNITS: Record<string, number> = {\n ms: 1,\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n};\n\nconst DURATION_RE = /^(\\d+)(ms|s|m|h|d)$/;\n\nexport function parseDuration(value: string | number): number {\n if (typeof value === 'number') return value;\n\n const match = value.match(DURATION_RE);\n if (!match) {\n throw new Error(`Invalid duration: \"${value}\". Use format like \"5m\", \"1h\", \"30s\", \"100ms\", \"1d\"`);\n }\n\n return parseInt(match[1], 10) * UNITS[match[2]];\n}\n","import type { CacheOptions, SetOptions, WrapOptions, CacheStats, CacheEntry, SerializedCache } from './types.js';\nimport { parseDuration } from './duration.js';\n\nexport function createCache<V = unknown>(options: CacheOptions = {}) {\n const { maxItems = Infinity } = options;\n const defaultTTL = options.defaultTTL ? parseDuration(options.defaultTTL) : null;\n\n const store = new Map<string, CacheEntry<V>>();\n const tagIndex = new Map<string, Set<string>>();\n let hits = 0;\n let misses = 0;\n\n function evictLRU(): void {\n if (store.size <= maxItems) return;\n const firstKey = store.keys().next().value;\n if (firstKey !== undefined) {\n deleteEntry(firstKey);\n }\n }\n\n function deleteEntry(key: string): void {\n const entry = store.get(key);\n if (entry) {\n for (const tag of entry.tags) {\n const keys = tagIndex.get(tag);\n if (keys) {\n keys.delete(key);\n if (keys.size === 0) tagIndex.delete(tag);\n }\n }\n store.delete(key);\n }\n }\n\n function isExpired(entry: CacheEntry<V>): boolean {\n if (entry.expiresAt === null) return false;\n return Date.now() > entry.expiresAt;\n }\n\n function isStale(entry: CacheEntry<V>): boolean {\n if (entry.staleAt === null) return false;\n return Date.now() > entry.staleAt;\n }\n\n function set(key: string, value: V, opts?: SetOptions): void {\n deleteEntry(key);\n\n const ttl = opts?.ttl ? parseDuration(opts.ttl) : defaultTTL;\n const swr = opts?.staleWhileRevalidate ? parseDuration(opts.staleWhileRevalidate) : null;\n const now = Date.now();\n const tags = opts?.tags ?? [];\n\n const entry: CacheEntry<V> = {\n value,\n expiresAt: ttl ? now + ttl : null,\n staleAt: ttl && swr ? now + ttl - swr : null,\n tags,\n };\n\n store.set(key, entry);\n\n for (const tag of tags) {\n let keys = tagIndex.get(tag);\n if (!keys) {\n keys = new Set();\n tagIndex.set(tag, keys);\n }\n keys.add(key);\n }\n\n evictLRU();\n }\n\n function get(key: string): V | undefined {\n const entry = store.get(key);\n if (!entry) {\n misses++;\n return undefined;\n }\n\n if (isExpired(entry)) {\n deleteEntry(key);\n misses++;\n return undefined;\n }\n\n hits++;\n // Move to end for LRU\n store.delete(key);\n store.set(key, entry);\n\n return entry.value;\n }\n\n function has(key: string): boolean {\n const entry = store.get(key);\n if (!entry) return false;\n if (isExpired(entry)) {\n deleteEntry(key);\n return false;\n }\n return true;\n }\n\n function del(key: string): boolean {\n if (!store.has(key)) return false;\n deleteEntry(key);\n return true;\n }\n\n function clear(): void {\n store.clear();\n tagIndex.clear();\n hits = 0;\n misses = 0;\n }\n\n function invalidateTag(tag: string): number {\n const keys = tagIndex.get(tag);\n if (!keys) return 0;\n const count = keys.size;\n for (const key of [...keys]) {\n deleteEntry(key);\n }\n return count;\n }\n\n function wrap<TArgs extends unknown[], TReturn extends V>(\n keyPrefix: string,\n fn: (...args: TArgs) => Promise<TReturn>,\n opts?: WrapOptions,\n ): (...args: TArgs) => Promise<TReturn> {\n const revalidating = new Set<string>();\n\n return async (...args: TArgs): Promise<TReturn> => {\n const cacheKey = `${keyPrefix}:${JSON.stringify(args)}`;\n const entry = store.get(cacheKey);\n\n if (entry && !isExpired(entry)) {\n hits++;\n // Move to end for LRU\n store.delete(cacheKey);\n store.set(cacheKey, entry);\n\n // Stale-while-revalidate\n if (isStale(entry) && !revalidating.has(cacheKey)) {\n revalidating.add(cacheKey);\n fn(...args)\n .then((value) => set(cacheKey, value, opts))\n .catch((err: unknown) => {\n if (opts?.onRevalidateError) {\n opts.onRevalidateError(err instanceof Error ? err : new Error(String(err)), cacheKey);\n }\n })\n .finally(() => revalidating.delete(cacheKey));\n }\n\n return entry.value as TReturn;\n }\n\n misses++;\n const value = await fn(...args);\n set(cacheKey, value, opts);\n return value;\n };\n }\n\n function stats(): CacheStats {\n const total = hits + misses;\n return {\n hits,\n misses,\n hitRate: total === 0 ? 0 : hits / total,\n size: store.size,\n };\n }\n\n function dump(): SerializedCache {\n const entries: SerializedCache['entries'] = [];\n for (const [key, entry] of store) {\n entries.push([key, { ...entry, key }]);\n }\n return { entries };\n }\n\n function load(data: SerializedCache): void {\n clear();\n for (const [key, entry] of data.entries) {\n const { key: _key, ...rest } = entry;\n store.set(key, rest as CacheEntry<V>);\n for (const tag of rest.tags) {\n let keys = tagIndex.get(tag);\n if (!keys) {\n keys = new Set();\n tagIndex.set(tag, keys);\n }\n keys.add(key);\n }\n }\n }\n\n function keys(): string[] {\n const result: string[] = [];\n for (const [key, entry] of store) {\n if (isExpired(entry)) {\n deleteEntry(key);\n } else {\n result.push(key);\n }\n }\n return result;\n }\n\n function size(): number {\n return store.size;\n }\n\n return { set, get, has, delete: del, clear, invalidateTag, wrap, stats, dump, load, keys, size };\n}\n"]}
|