@philiprehberger/cache-kit 0.2.2 → 0.3.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/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # @philiprehberger/ts-cache-kit
1
+ # @philiprehberger/cache-kit
2
2
 
3
3
  In-memory LRU cache with TTL, stale-while-revalidate, and tag invalidation.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @philiprehberger/ts-cache-kit
8
+ npm install @philiprehberger/cache-kit
9
9
  ```
10
10
 
11
11
  ## Usage
@@ -13,7 +13,7 @@ npm install @philiprehberger/ts-cache-kit
13
13
  ### Basic
14
14
 
15
15
  ```ts
16
- import { createCache } from '@philiprehberger/ts-cache-kit';
16
+ import { createCache } from '@philiprehberger/cache-kit';
17
17
 
18
18
  const cache = createCache({ maxItems: 1000, defaultTTL: '5m' });
19
19
 
@@ -67,9 +67,21 @@ const same = await getUser('123'); // served from cache
67
67
  const getUser = cache.wrap(
68
68
  'user',
69
69
  (id: string) => db.users.findById(id),
70
- { ttl: '5m', staleWhileRevalidate: '1m' },
70
+ {
71
+ ttl: '5m',
72
+ staleWhileRevalidate: '1m',
73
+ onRevalidateError: (err, key) => console.error(`SWR failed for ${key}:`, err),
74
+ },
71
75
  );
72
76
  // Serves stale data immediately while refreshing in the background
77
+ // If revalidation fails, onRevalidateError is called instead of silently swallowing the error
78
+ ```
79
+
80
+ ### Keys & Size
81
+
82
+ ```ts
83
+ cache.keys(); // ['user:1', 'user:2'] — all non-expired keys (lazy-cleans expired entries)
84
+ cache.size(); // 2 — number of entries in the store
73
85
  ```
74
86
 
75
87
  ### Stats
@@ -100,6 +112,8 @@ cache.load(data); // restore from JSON
100
112
  | `invalidateTag` | `(tag: string) => number` | Remove all entries with the given tag. Returns count removed. |
101
113
  | `wrap` | `(keyPrefix, fn, opts?) => (...args) => Promise` | Memoize an async function with cache. |
102
114
  | `stats` | `() => CacheStats` | Get hit/miss/size statistics. |
115
+ | `keys` | `() => string[]` | Return all non-expired keys. Lazy-cleans expired entries. |
116
+ | `size` | `() => number` | Return the number of entries in the store. |
103
117
  | `dump` | `() => SerializedCache` | Serialize cache contents for persistence. |
104
118
  | `load` | `(data: SerializedCache) => void` | Restore cache from serialized data. |
105
119
 
@@ -118,6 +132,14 @@ cache.load(data); // restore from JSON
118
132
  | `tags` | `string[]` | Tags for group invalidation. |
119
133
  | `staleWhileRevalidate` | `string \| number` | Window before expiry where stale data is served while refreshing. |
120
134
 
135
+ ### `WrapOptions`
136
+
137
+ Extends `SetOptions` with:
138
+
139
+ | Property | Type | Description |
140
+ |----------|------|-------------|
141
+ | `onRevalidateError` | `(error: Error, key: string) => void` | Called when a stale-while-revalidate background refresh fails. If omitted, errors are silently swallowed. |
142
+
121
143
  ### Duration Strings
122
144
 
123
145
  `"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 keys = tagIndex.get(tag);
73
- if (keys) {
74
- keys.delete(key);
75
- if (keys.size === 0) tagIndex.delete(tag);
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 keys = tagIndex.get(tag);
105
- if (!keys) {
106
- keys = /* @__PURE__ */ new Set();
107
- tagIndex.set(tag, keys);
104
+ let keys2 = tagIndex.get(tag);
105
+ if (!keys2) {
106
+ keys2 = /* @__PURE__ */ new Set();
107
+ tagIndex.set(tag, keys2);
108
108
  }
109
- keys.add(key);
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 keys = tagIndex.get(tag);
151
- if (!keys) return 0;
152
- const count = keys.size;
153
- for (const key of [...keys]) {
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 keys = tagIndex.get(tag);
203
- if (!keys) {
204
- keys = /* @__PURE__ */ new Set();
205
- tagIndex.set(tag, keys);
205
+ let keys2 = tagIndex.get(tag);
206
+ if (!keys2) {
207
+ keys2 = /* @__PURE__ */ new Set();
208
+ tagIndex.set(tag, keys2);
206
209
  }
207
- keys.add(key);
210
+ keys2.add(key);
208
211
  }
209
212
  }
210
213
  }
211
- return { set, get, has, delete: del, clear, invalidateTag, wrap, stats, dump, load };
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;
@@ -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 keys = tagIndex.get(tag);
71
- if (keys) {
72
- keys.delete(key);
73
- if (keys.size === 0) tagIndex.delete(tag);
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 keys = tagIndex.get(tag);
103
- if (!keys) {
104
- keys = /* @__PURE__ */ new Set();
105
- tagIndex.set(tag, keys);
102
+ let keys2 = tagIndex.get(tag);
103
+ if (!keys2) {
104
+ keys2 = /* @__PURE__ */ new Set();
105
+ tagIndex.set(tag, keys2);
106
106
  }
107
- keys.add(key);
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 keys = tagIndex.get(tag);
149
- if (!keys) return 0;
150
- const count = keys.size;
151
- for (const key of [...keys]) {
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 keys = tagIndex.get(tag);
201
- if (!keys) {
202
- keys = /* @__PURE__ */ new Set();
203
- tagIndex.set(tag, keys);
203
+ let keys2 = tagIndex.get(tag);
204
+ if (!keys2) {
205
+ keys2 = /* @__PURE__ */ new Set();
206
+ tagIndex.set(tag, keys2);
204
207
  }
205
- keys.add(key);
208
+ keys2.add(key);
206
209
  }
207
210
  }
208
211
  }
209
- return { set, get, has, delete: del, clear, invalidateTag, wrap, stats, dump, load };
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@philiprehberger/cache-kit",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "In-memory LRU cache with TTL, stale-while-revalidate, and tag invalidation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",