@open-mercato/cache 0.3.2 → 0.4.2-canary-b8ab8f2d43

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.
Files changed (45) hide show
  1. package/.test-cache.json +4 -0
  2. package/build.mjs +61 -0
  3. package/dist/errors.js +12 -8
  4. package/dist/errors.js.map +7 -0
  5. package/dist/index.js +16 -7
  6. package/dist/index.js.map +7 -0
  7. package/dist/service.js +240 -287
  8. package/dist/service.js.map +7 -0
  9. package/dist/strategies/jsonfile.js +179 -194
  10. package/dist/strategies/jsonfile.js.map +7 -0
  11. package/dist/strategies/memory.js +143 -157
  12. package/dist/strategies/memory.js.map +7 -0
  13. package/dist/strategies/redis.js +292 -359
  14. package/dist/strategies/redis.js.map +7 -0
  15. package/dist/strategies/sqlite.js +164 -191
  16. package/dist/strategies/sqlite.js.map +7 -0
  17. package/dist/tenantContext.js +10 -6
  18. package/dist/tenantContext.js.map +7 -0
  19. package/dist/types.js +1 -1
  20. package/dist/types.js.map +7 -0
  21. package/jest.config.cjs +19 -0
  22. package/package.json +40 -12
  23. package/src/__tests__/memory.strategy.test.ts +15 -7
  24. package/tsconfig.build.json +9 -1
  25. package/tsconfig.json +4 -7
  26. package/watch.mjs +6 -0
  27. package/dist/errors.d.ts +0 -7
  28. package/dist/errors.d.ts.map +0 -1
  29. package/dist/index.d.ts +0 -8
  30. package/dist/index.d.ts.map +0 -1
  31. package/dist/service.d.ts +0 -40
  32. package/dist/service.d.ts.map +0 -1
  33. package/dist/strategies/jsonfile.d.ts +0 -10
  34. package/dist/strategies/jsonfile.d.ts.map +0 -1
  35. package/dist/strategies/memory.d.ts +0 -9
  36. package/dist/strategies/memory.d.ts.map +0 -1
  37. package/dist/strategies/redis.d.ts +0 -5
  38. package/dist/strategies/redis.d.ts.map +0 -1
  39. package/dist/strategies/sqlite.d.ts +0 -13
  40. package/dist/strategies/sqlite.d.ts.map +0 -1
  41. package/dist/tenantContext.d.ts +0 -4
  42. package/dist/tenantContext.d.ts.map +0 -1
  43. package/dist/types.d.ts +0 -86
  44. package/dist/types.d.ts.map +0 -1
  45. package/jest.config.js +0 -19
package/dist/service.js CHANGED
@@ -1,321 +1,274 @@
1
- import { createMemoryStrategy } from './strategies/memory';
2
- import { createRedisStrategy } from './strategies/redis';
3
- import { createSqliteStrategy } from './strategies/sqlite';
4
- import { createJsonFileStrategy } from './strategies/jsonfile';
5
- import { getCurrentCacheTenant } from './tenantContext';
6
- import { createHash } from 'node:crypto';
7
- import { CacheDependencyUnavailableError } from './errors';
1
+ import { createMemoryStrategy } from "./strategies/memory.js";
2
+ import { createRedisStrategy } from "./strategies/redis.js";
3
+ import { createSqliteStrategy } from "./strategies/sqlite.js";
4
+ import { createJsonFileStrategy } from "./strategies/jsonfile.js";
5
+ import { getCurrentCacheTenant } from "./tenantContext.js";
6
+ import { createHash } from "node:crypto";
7
+ import { CacheDependencyUnavailableError } from "./errors.js";
8
8
  function normalizeTenantKey(raw) {
9
- const value = typeof raw === 'string' ? raw.trim() : '';
10
- if (!value)
11
- return 'global';
12
- return value.replace(/[^a-zA-Z0-9._-]/g, '_');
9
+ const value = typeof raw === "string" ? raw.trim() : "";
10
+ if (!value) return "global";
11
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_");
13
12
  }
14
13
  function isCacheMetadata(value) {
15
- if (typeof value !== 'object' || value === null) {
16
- return false;
17
- }
18
- const record = value;
19
- const hasValidKey = typeof record.key === 'string';
20
- const hasValidExpiresAt = !('expiresAt' in record)
21
- || record.expiresAt === null
22
- || typeof record.expiresAt === 'number';
23
- return hasValidKey && hasValidExpiresAt;
14
+ if (typeof value !== "object" || value === null) {
15
+ return false;
16
+ }
17
+ const record = value;
18
+ const hasValidKey = typeof record.key === "string";
19
+ const hasValidExpiresAt = !("expiresAt" in record) || record.expiresAt === null || typeof record.expiresAt === "number";
20
+ return hasValidKey && hasValidExpiresAt;
24
21
  }
25
- const KNOWN_STRATEGIES = ['memory', 'redis', 'sqlite', 'jsonfile'];
22
+ const KNOWN_STRATEGIES = ["memory", "redis", "sqlite", "jsonfile"];
26
23
  function isCacheStrategyName(value) {
27
- if (!value)
28
- return false;
29
- return KNOWN_STRATEGIES.includes(value);
24
+ if (!value) return false;
25
+ return KNOWN_STRATEGIES.includes(value);
30
26
  }
31
27
  function resolveTenantPrefixes() {
32
- const tenant = normalizeTenantKey(getCurrentCacheTenant());
33
- const base = `tenant:${tenant}:`;
34
- return {
35
- keyPrefix: `${base}key:`,
36
- tagPrefix: `${base}tag:`,
37
- scopeTag: `${base}tag:__scope__`,
38
- };
28
+ const tenant = normalizeTenantKey(getCurrentCacheTenant());
29
+ const base = `tenant:${tenant}:`;
30
+ return {
31
+ keyPrefix: `${base}key:`,
32
+ tagPrefix: `${base}tag:`,
33
+ scopeTag: `${base}tag:__scope__`
34
+ };
39
35
  }
40
36
  function hashIdentifier(input) {
41
- return createHash('sha1').update(input).digest('hex');
37
+ return createHash("sha1").update(input).digest("hex");
42
38
  }
43
39
  function storageKey(originalKey, prefixes) {
44
- return `${prefixes.keyPrefix}k:${hashIdentifier(originalKey)}`;
40
+ return `${prefixes.keyPrefix}k:${hashIdentifier(originalKey)}`;
45
41
  }
46
42
  function metaKey(originalKey, prefixes) {
47
- return `${prefixes.keyPrefix}meta:${hashIdentifier(originalKey)}`;
43
+ return `${prefixes.keyPrefix}meta:${hashIdentifier(originalKey)}`;
48
44
  }
49
45
  function hashedTag(tag, prefixes) {
50
- return `${prefixes.tagPrefix}t:${hashIdentifier(tag)}`;
46
+ return `${prefixes.tagPrefix}t:${hashIdentifier(tag)}`;
51
47
  }
52
48
  function buildTagSet(tags, prefixes, includeScope) {
53
- const scoped = new Set();
54
- if (includeScope)
55
- scoped.add(prefixes.scopeTag);
56
- if (Array.isArray(tags)) {
57
- for (const tag of tags) {
58
- if (typeof tag === 'string' && tag.length > 0)
59
- scoped.add(hashedTag(tag, prefixes));
60
- }
49
+ const scoped = /* @__PURE__ */ new Set();
50
+ if (includeScope) scoped.add(prefixes.scopeTag);
51
+ if (Array.isArray(tags)) {
52
+ for (const tag of tags) {
53
+ if (typeof tag === "string" && tag.length > 0) scoped.add(hashedTag(tag, prefixes));
61
54
  }
62
- return Array.from(scoped);
55
+ }
56
+ return Array.from(scoped);
63
57
  }
64
58
  function matchPattern(value, pattern) {
65
- const regexPattern = pattern
66
- .replace(/[.+^${}()|[\]\\]/g, '\\$&')
67
- .replace(/\*/g, '.*')
68
- .replace(/\?/g, '.');
69
- const regex = new RegExp(`^${regexPattern}$`);
70
- return regex.test(value);
59
+ const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
60
+ const regex = new RegExp(`^${regexPattern}$`);
61
+ return regex.test(value);
71
62
  }
72
63
  function createTenantAwareWrapper(base) {
73
- function normalizeDeletionCount(raw) {
74
- if (!raw)
75
- return raw;
76
- if (!Number.isFinite(raw))
77
- return raw;
78
- return Math.ceil(raw / 2);
64
+ function normalizeDeletionCount(raw) {
65
+ if (!raw) return raw;
66
+ if (!Number.isFinite(raw)) return raw;
67
+ return Math.ceil(raw / 2);
68
+ }
69
+ const get = async (key, options) => {
70
+ const prefixes = resolveTenantPrefixes();
71
+ return base.get(storageKey(key, prefixes), options);
72
+ };
73
+ const set = async (key, value, options) => {
74
+ const prefixes = resolveTenantPrefixes();
75
+ const hashedTags = buildTagSet(options?.tags, prefixes, true);
76
+ const ttl = options?.ttl ?? void 0;
77
+ const nextOptions = options ? { ...options, tags: hashedTags } : { tags: hashedTags };
78
+ await base.set(storageKey(key, prefixes), value, nextOptions);
79
+ const metaPayload = { key, expiresAt: ttl ? Date.now() + ttl : null };
80
+ await base.set(metaKey(key, prefixes), metaPayload, {
81
+ ttl,
82
+ tags: hashedTags
83
+ });
84
+ };
85
+ const has = async (key) => {
86
+ const prefixes = resolveTenantPrefixes();
87
+ return base.has(storageKey(key, prefixes));
88
+ };
89
+ const del = async (key) => {
90
+ const prefixes = resolveTenantPrefixes();
91
+ const primary = await base.delete(storageKey(key, prefixes));
92
+ await base.delete(metaKey(key, prefixes));
93
+ return primary;
94
+ };
95
+ const deleteByTags = async (tags) => {
96
+ const prefixes = resolveTenantPrefixes();
97
+ const scopedTags = buildTagSet(tags, prefixes, false);
98
+ if (!scopedTags.length) return 0;
99
+ const removed = await base.deleteByTags(scopedTags);
100
+ return normalizeDeletionCount(removed);
101
+ };
102
+ const clear = async () => {
103
+ const prefixes = resolveTenantPrefixes();
104
+ const removed = await base.deleteByTags([prefixes.scopeTag]);
105
+ return normalizeDeletionCount(removed);
106
+ };
107
+ const keys = async (pattern) => {
108
+ const prefixes = resolveTenantPrefixes();
109
+ const metaPattern = `${prefixes.keyPrefix}meta:*`;
110
+ const metaKeys = await base.keys(metaPattern);
111
+ const originals = [];
112
+ for (const metaKey2 of metaKeys) {
113
+ const metaValue = await base.get(metaKey2, { returnExpired: true });
114
+ if (!metaValue) continue;
115
+ const metadata = typeof metaValue === "string" ? null : isCacheMetadata(metaValue) ? metaValue : null;
116
+ const original = typeof metaValue === "string" ? metaValue : metadata?.key;
117
+ if (!original) continue;
118
+ if (pattern && !matchPattern(original, pattern)) continue;
119
+ originals.push(original);
79
120
  }
80
- const get = async (key, options) => {
81
- const prefixes = resolveTenantPrefixes();
82
- return base.get(storageKey(key, prefixes), options);
83
- };
84
- const set = async (key, value, options) => {
85
- var _a;
86
- const prefixes = resolveTenantPrefixes();
87
- const hashedTags = buildTagSet(options === null || options === void 0 ? void 0 : options.tags, prefixes, true);
88
- const ttl = (_a = options === null || options === void 0 ? void 0 : options.ttl) !== null && _a !== void 0 ? _a : undefined;
89
- const nextOptions = options
90
- ? Object.assign(Object.assign({}, options), { tags: hashedTags }) : { tags: hashedTags };
91
- await base.set(storageKey(key, prefixes), value, nextOptions);
92
- const metaPayload = { key, expiresAt: ttl ? Date.now() + ttl : null };
93
- await base.set(metaKey(key, prefixes), metaPayload, {
94
- ttl,
95
- tags: hashedTags,
96
- });
97
- };
98
- const has = async (key) => {
99
- const prefixes = resolveTenantPrefixes();
100
- return base.has(storageKey(key, prefixes));
101
- };
102
- const del = async (key) => {
103
- const prefixes = resolveTenantPrefixes();
104
- const primary = await base.delete(storageKey(key, prefixes));
105
- await base.delete(metaKey(key, prefixes));
106
- return primary;
107
- };
108
- const deleteByTags = async (tags) => {
109
- const prefixes = resolveTenantPrefixes();
110
- const scopedTags = buildTagSet(tags, prefixes, false);
111
- if (!scopedTags.length)
112
- return 0;
113
- const removed = await base.deleteByTags(scopedTags);
114
- return normalizeDeletionCount(removed);
115
- };
116
- const clear = async () => {
117
- const prefixes = resolveTenantPrefixes();
118
- const removed = await base.deleteByTags([prefixes.scopeTag]);
119
- return normalizeDeletionCount(removed);
120
- };
121
- const keys = async (pattern) => {
122
- const prefixes = resolveTenantPrefixes();
123
- const metaPattern = `${prefixes.keyPrefix}meta:*`;
124
- const metaKeys = await base.keys(metaPattern);
125
- const originals = [];
126
- for (const metaKey of metaKeys) {
127
- const metaValue = await base.get(metaKey, { returnExpired: true });
128
- if (!metaValue)
129
- continue;
130
- const metadata = typeof metaValue === 'string' ? null : (isCacheMetadata(metaValue) ? metaValue : null);
131
- const original = typeof metaValue === 'string' ? metaValue : metadata === null || metadata === void 0 ? void 0 : metadata.key;
132
- if (!original)
133
- continue;
134
- if (pattern && !matchPattern(original, pattern))
135
- continue;
136
- originals.push(original);
137
- }
138
- return originals;
139
- };
140
- const stats = async () => {
141
- var _a;
142
- const prefixes = resolveTenantPrefixes();
143
- const metaKeys = await base.keys(`${prefixes.keyPrefix}meta:*`);
144
- let size = 0;
145
- let expired = 0;
146
- const now = Date.now();
147
- for (const metaKey of metaKeys) {
148
- const metaValue = await base.get(metaKey, { returnExpired: true });
149
- if (!metaValue)
150
- continue;
151
- const metadata = typeof metaValue === 'string' ? null : (isCacheMetadata(metaValue) ? metaValue : null);
152
- const original = typeof metaValue === 'string' ? metaValue : metadata === null || metadata === void 0 ? void 0 : metadata.key;
153
- if (!original)
154
- continue;
155
- size++;
156
- const expiresAt = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.expiresAt) !== null && _a !== void 0 ? _a : null;
157
- if (expiresAt !== null && expiresAt <= now)
158
- expired++;
159
- }
160
- return { size, expired };
161
- };
162
- const cleanup = base.cleanup
163
- ? async () => normalizeDeletionCount(await base.cleanup())
164
- : undefined;
165
- const close = base.close
166
- ? async () => base.close()
167
- : undefined;
168
- return {
169
- get,
170
- set,
171
- has,
172
- delete: del,
173
- deleteByTags,
174
- clear,
175
- keys,
176
- stats,
177
- cleanup,
178
- close,
179
- };
121
+ return originals;
122
+ };
123
+ const stats = async () => {
124
+ const prefixes = resolveTenantPrefixes();
125
+ const metaKeys = await base.keys(`${prefixes.keyPrefix}meta:*`);
126
+ let size = 0;
127
+ let expired = 0;
128
+ const now = Date.now();
129
+ for (const metaKey2 of metaKeys) {
130
+ const metaValue = await base.get(metaKey2, { returnExpired: true });
131
+ if (!metaValue) continue;
132
+ const metadata = typeof metaValue === "string" ? null : isCacheMetadata(metaValue) ? metaValue : null;
133
+ const original = typeof metaValue === "string" ? metaValue : metadata?.key;
134
+ if (!original) continue;
135
+ size++;
136
+ const expiresAt = metadata?.expiresAt ?? null;
137
+ if (expiresAt !== null && expiresAt <= now) expired++;
138
+ }
139
+ return { size, expired };
140
+ };
141
+ const cleanup = base.cleanup ? async () => normalizeDeletionCount(await base.cleanup()) : void 0;
142
+ const close = base.close ? async () => base.close() : void 0;
143
+ return {
144
+ get,
145
+ set,
146
+ has,
147
+ delete: del,
148
+ deleteByTags,
149
+ clear,
150
+ keys,
151
+ stats,
152
+ cleanup,
153
+ close
154
+ };
180
155
  }
181
- /**
182
- * Cache service that provides a unified interface to different cache strategies
183
- *
184
- * Configuration via environment variables:
185
- * - CACHE_STRATEGY: 'memory' | 'redis' | 'sqlite' | 'jsonfile' (default: 'memory')
186
- * - CACHE_TTL: Default TTL in milliseconds (optional)
187
- * - CACHE_REDIS_URL: Redis connection URL (for redis strategy)
188
- * - CACHE_SQLITE_PATH: SQLite database file path (for sqlite strategy)
189
- * - CACHE_JSON_FILE_PATH: JSON file path (for jsonfile strategy)
190
- *
191
- * @example
192
- * const cache = createCacheService({ strategy: 'memory', defaultTtl: 60000 })
193
- * await cache.set('user:123', { name: 'John' }, { tags: ['users', 'user:123'] })
194
- * const user = await cache.get('user:123')
195
- * await cache.deleteByTags(['users']) // Invalidate all user-related cache
196
- */
197
- export function createCacheService(options) {
198
- var _a, _b, _c;
199
- const envStrategy = isCacheStrategyName(process.env.CACHE_STRATEGY)
200
- ? process.env.CACHE_STRATEGY
201
- : undefined;
202
- const strategyType = (_b = (_a = options === null || options === void 0 ? void 0 : options.strategy) !== null && _a !== void 0 ? _a : envStrategy) !== null && _b !== void 0 ? _b : 'memory';
203
- const envTtl = process.env.CACHE_TTL;
204
- const parsedEnvTtl = envTtl ? Number.parseInt(envTtl, 10) : undefined;
205
- const defaultTtl = (_c = options === null || options === void 0 ? void 0 : options.defaultTtl) !== null && _c !== void 0 ? _c : (typeof parsedEnvTtl === 'number' && Number.isFinite(parsedEnvTtl) ? parsedEnvTtl : undefined);
206
- const baseStrategy = createStrategyForType(strategyType, options, defaultTtl);
207
- const resilientStrategy = withDependencyFallback(baseStrategy, strategyType, defaultTtl);
208
- return createTenantAwareWrapper(resilientStrategy);
156
+ function createCacheService(options) {
157
+ const envStrategy = isCacheStrategyName(process.env.CACHE_STRATEGY) ? process.env.CACHE_STRATEGY : void 0;
158
+ const strategyType = options?.strategy ?? envStrategy ?? "memory";
159
+ const envTtl = process.env.CACHE_TTL;
160
+ const parsedEnvTtl = envTtl ? Number.parseInt(envTtl, 10) : void 0;
161
+ const defaultTtl = options?.defaultTtl ?? (typeof parsedEnvTtl === "number" && Number.isFinite(parsedEnvTtl) ? parsedEnvTtl : void 0);
162
+ const baseStrategy = createStrategyForType(strategyType, options, defaultTtl);
163
+ const resilientStrategy = withDependencyFallback(baseStrategy, strategyType, defaultTtl);
164
+ return createTenantAwareWrapper(resilientStrategy);
209
165
  }
210
- /**
211
- * CacheService class wrapper for DI integration
212
- * Provides the same interface as the functional API but as a class
213
- */
214
- export class CacheService {
215
- constructor(options) {
216
- this.strategy = createCacheService(options);
217
- }
218
- async get(key, options) {
219
- return this.strategy.get(key, options);
166
+ class CacheService {
167
+ constructor(options) {
168
+ this.strategy = createCacheService(options);
169
+ }
170
+ async get(key, options) {
171
+ return this.strategy.get(key, options);
172
+ }
173
+ async set(key, value, options) {
174
+ return this.strategy.set(key, value, options);
175
+ }
176
+ async has(key) {
177
+ return this.strategy.has(key);
178
+ }
179
+ async delete(key) {
180
+ return this.strategy.delete(key);
181
+ }
182
+ async deleteByTags(tags) {
183
+ return this.strategy.deleteByTags(tags);
184
+ }
185
+ async clear() {
186
+ return this.strategy.clear();
187
+ }
188
+ async keys(pattern) {
189
+ return this.strategy.keys(pattern);
190
+ }
191
+ async stats() {
192
+ return this.strategy.stats();
193
+ }
194
+ async cleanup() {
195
+ if (this.strategy.cleanup) {
196
+ return this.strategy.cleanup();
220
197
  }
221
- async set(key, value, options) {
222
- return this.strategy.set(key, value, options);
223
- }
224
- async has(key) {
225
- return this.strategy.has(key);
226
- }
227
- async delete(key) {
228
- return this.strategy.delete(key);
229
- }
230
- async deleteByTags(tags) {
231
- return this.strategy.deleteByTags(tags);
232
- }
233
- async clear() {
234
- return this.strategy.clear();
235
- }
236
- async keys(pattern) {
237
- return this.strategy.keys(pattern);
238
- }
239
- async stats() {
240
- return this.strategy.stats();
241
- }
242
- async cleanup() {
243
- if (this.strategy.cleanup) {
244
- return this.strategy.cleanup();
245
- }
246
- return 0;
247
- }
248
- async close() {
249
- if (this.strategy.close) {
250
- return this.strategy.close();
251
- }
198
+ return 0;
199
+ }
200
+ async close() {
201
+ if (this.strategy.close) {
202
+ return this.strategy.close();
252
203
  }
204
+ }
253
205
  }
254
206
  function createStrategyForType(strategyType, options, defaultTtl) {
255
- switch (strategyType) {
256
- case 'redis':
257
- return createRedisStrategy(options === null || options === void 0 ? void 0 : options.redisUrl, { defaultTtl });
258
- case 'sqlite':
259
- return createSqliteStrategy(options === null || options === void 0 ? void 0 : options.sqlitePath, { defaultTtl });
260
- case 'jsonfile':
261
- return createJsonFileStrategy(options === null || options === void 0 ? void 0 : options.jsonFilePath, { defaultTtl });
262
- case 'memory':
263
- default:
264
- return createMemoryStrategy({ defaultTtl });
265
- }
207
+ switch (strategyType) {
208
+ case "redis":
209
+ return createRedisStrategy(options?.redisUrl, { defaultTtl });
210
+ case "sqlite":
211
+ return createSqliteStrategy(options?.sqlitePath, { defaultTtl });
212
+ case "jsonfile":
213
+ return createJsonFileStrategy(options?.jsonFilePath, { defaultTtl });
214
+ case "memory":
215
+ default:
216
+ return createMemoryStrategy({ defaultTtl });
217
+ }
266
218
  }
267
219
  function withDependencyFallback(strategy, strategyType, defaultTtl) {
268
- if (strategyType === 'memory')
269
- return strategy;
270
- let activeStrategy = strategy;
271
- let fallbackStrategy = null;
272
- let warned = false;
273
- const ensureFallback = (error) => {
274
- if (!fallbackStrategy) {
275
- fallbackStrategy = createMemoryStrategy({ defaultTtl });
276
- }
277
- if (!warned) {
278
- const dependencyMessage = error.dependency
279
- ? ` (missing dependency: ${error.dependency})`
280
- : '';
281
- console.warn(`[cache] ${error.strategy} strategy unavailable${dependencyMessage}. Falling back to memory strategy.`);
282
- warned = true;
220
+ if (strategyType === "memory") return strategy;
221
+ let activeStrategy = strategy;
222
+ let fallbackStrategy = null;
223
+ let warned = false;
224
+ const ensureFallback = (error) => {
225
+ if (!fallbackStrategy) {
226
+ fallbackStrategy = createMemoryStrategy({ defaultTtl });
227
+ }
228
+ if (!warned) {
229
+ const dependencyMessage = error.dependency ? ` (missing dependency: ${error.dependency})` : "";
230
+ console.warn(`[cache] ${error.strategy} strategy unavailable${dependencyMessage}. Falling back to memory strategy.`);
231
+ warned = true;
232
+ }
233
+ activeStrategy = fallbackStrategy;
234
+ };
235
+ const wrapMethod = (method) => {
236
+ const handler = async (...args) => {
237
+ const fn = activeStrategy[method];
238
+ if (!fn) {
239
+ return void 0;
240
+ }
241
+ try {
242
+ return await fn(...args);
243
+ } catch (error) {
244
+ if (error instanceof CacheDependencyUnavailableError) {
245
+ ensureFallback(error);
246
+ const fallbackFn = activeStrategy[method];
247
+ if (!fallbackFn) {
248
+ return void 0;
249
+ }
250
+ return fallbackFn(...args);
283
251
  }
284
- activeStrategy = fallbackStrategy;
285
- };
286
- const wrapMethod = (method) => {
287
- const handler = async (...args) => {
288
- const fn = activeStrategy[method];
289
- if (!fn) {
290
- return undefined;
291
- }
292
- try {
293
- return await fn(...args);
294
- }
295
- catch (error) {
296
- if (error instanceof CacheDependencyUnavailableError) {
297
- ensureFallback(error);
298
- const fallbackFn = activeStrategy[method];
299
- if (!fallbackFn) {
300
- return undefined;
301
- }
302
- return fallbackFn(...args);
303
- }
304
- throw error;
305
- }
306
- };
307
- return handler;
308
- };
309
- return {
310
- get: wrapMethod('get'),
311
- set: wrapMethod('set'),
312
- has: wrapMethod('has'),
313
- delete: wrapMethod('delete'),
314
- deleteByTags: wrapMethod('deleteByTags'),
315
- clear: wrapMethod('clear'),
316
- keys: wrapMethod('keys'),
317
- stats: wrapMethod('stats'),
318
- cleanup: typeof strategy.cleanup === 'function' ? wrapMethod('cleanup') : undefined,
319
- close: typeof strategy.close === 'function' ? wrapMethod('close') : undefined,
252
+ throw error;
253
+ }
320
254
  };
255
+ return handler;
256
+ };
257
+ return {
258
+ get: wrapMethod("get"),
259
+ set: wrapMethod("set"),
260
+ has: wrapMethod("has"),
261
+ delete: wrapMethod("delete"),
262
+ deleteByTags: wrapMethod("deleteByTags"),
263
+ clear: wrapMethod("clear"),
264
+ keys: wrapMethod("keys"),
265
+ stats: wrapMethod("stats"),
266
+ cleanup: typeof strategy.cleanup === "function" ? wrapMethod("cleanup") : void 0,
267
+ close: typeof strategy.close === "function" ? wrapMethod("close") : void 0
268
+ };
321
269
  }
270
+ export {
271
+ CacheService,
272
+ createCacheService
273
+ };
274
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/service.ts"],
4
+ "sourcesContent": ["import type { CacheStrategy, CacheServiceOptions, CacheGetOptions, CacheSetOptions, CacheValue } from './types'\nimport { createMemoryStrategy } from './strategies/memory'\nimport { createRedisStrategy } from './strategies/redis'\nimport { createSqliteStrategy } from './strategies/sqlite'\nimport { createJsonFileStrategy } from './strategies/jsonfile'\nimport { getCurrentCacheTenant } from './tenantContext'\nimport { createHash } from 'node:crypto'\nimport { CacheDependencyUnavailableError } from './errors'\n\nfunction normalizeTenantKey(raw: string | null | undefined): string {\n const value = typeof raw === 'string' ? raw.trim() : ''\n if (!value) return 'global'\n return value.replace(/[^a-zA-Z0-9._-]/g, '_')\n}\n\ntype TenantPrefixes = {\n keyPrefix: string\n tagPrefix: string\n scopeTag: string\n}\n\ntype CacheMetadata = {\n key: string\n expiresAt: number | null\n}\n\nfunction isCacheMetadata(value: CacheValue | null): value is CacheMetadata {\n if (typeof value !== 'object' || value === null) {\n return false\n }\n const record = value as Record<string, unknown>\n const hasValidKey = typeof record.key === 'string'\n const hasValidExpiresAt =\n !('expiresAt' in record)\n || record.expiresAt === null\n || typeof record.expiresAt === 'number'\n\n return hasValidKey && hasValidExpiresAt\n}\n\ntype CacheStrategyName = NonNullable<CacheServiceOptions['strategy']>\nconst KNOWN_STRATEGIES: CacheStrategyName[] = ['memory', 'redis', 'sqlite', 'jsonfile']\n\nfunction isCacheStrategyName(value: string | undefined): value is CacheStrategyName {\n if (!value) return false\n return KNOWN_STRATEGIES.includes(value as CacheStrategyName)\n}\n\nfunction resolveTenantPrefixes(): TenantPrefixes {\n const tenant = normalizeTenantKey(getCurrentCacheTenant())\n const base = `tenant:${tenant}:`\n return {\n keyPrefix: `${base}key:`,\n tagPrefix: `${base}tag:`,\n scopeTag: `${base}tag:__scope__`,\n }\n}\n\nfunction hashIdentifier(input: string): string {\n return createHash('sha1').update(input).digest('hex')\n}\n\nfunction storageKey(originalKey: string, prefixes: TenantPrefixes): string {\n return `${prefixes.keyPrefix}k:${hashIdentifier(originalKey)}`\n}\n\nfunction metaKey(originalKey: string, prefixes: TenantPrefixes): string {\n return `${prefixes.keyPrefix}meta:${hashIdentifier(originalKey)}`\n}\n\nfunction hashedTag(tag: string, prefixes: TenantPrefixes): string {\n return `${prefixes.tagPrefix}t:${hashIdentifier(tag)}`\n}\n\nfunction buildTagSet(tags: string[] | undefined, prefixes: TenantPrefixes, includeScope: boolean): string[] {\n const scoped = new Set<string>()\n if (includeScope) scoped.add(prefixes.scopeTag)\n if (Array.isArray(tags)) {\n for (const tag of tags) {\n if (typeof tag === 'string' && tag.length > 0) scoped.add(hashedTag(tag, prefixes))\n }\n }\n return Array.from(scoped)\n}\n\nfunction matchPattern(value: string, pattern: string): boolean {\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.')\n const regex = new RegExp(`^${regexPattern}$`)\n return regex.test(value)\n}\n\nfunction createTenantAwareWrapper(base: CacheStrategy): CacheStrategy {\n function normalizeDeletionCount(raw: number): number {\n if (!raw) return raw\n if (!Number.isFinite(raw)) return raw\n return Math.ceil(raw / 2)\n }\n\n const get = async (key: string, options?: CacheGetOptions) => {\n const prefixes = resolveTenantPrefixes()\n return base.get(storageKey(key, prefixes), options)\n }\n\n const set = async (key: string, value: CacheValue, options?: CacheSetOptions) => {\n const prefixes = resolveTenantPrefixes()\n const hashedTags = buildTagSet(options?.tags, prefixes, true)\n const ttl = options?.ttl ?? undefined\n const nextOptions: CacheSetOptions | undefined = options\n ? { ...options, tags: hashedTags }\n : { tags: hashedTags }\n await base.set(storageKey(key, prefixes), value, nextOptions)\n const metaPayload: CacheMetadata = { key, expiresAt: ttl ? Date.now() + ttl : null }\n await base.set(metaKey(key, prefixes), metaPayload, {\n ttl,\n tags: hashedTags,\n })\n }\n\n const has = async (key: string) => {\n const prefixes = resolveTenantPrefixes()\n return base.has(storageKey(key, prefixes))\n }\n\n const del = async (key: string) => {\n const prefixes = resolveTenantPrefixes()\n const primary = await base.delete(storageKey(key, prefixes))\n await base.delete(metaKey(key, prefixes))\n return primary\n }\n\n const deleteByTags = async (tags: string[]) => {\n const prefixes = resolveTenantPrefixes()\n const scopedTags = buildTagSet(tags, prefixes, false)\n if (!scopedTags.length) return 0\n const removed = await base.deleteByTags(scopedTags)\n return normalizeDeletionCount(removed)\n }\n\n const clear = async () => {\n const prefixes = resolveTenantPrefixes()\n const removed = await base.deleteByTags([prefixes.scopeTag])\n return normalizeDeletionCount(removed)\n }\n\n const keys = async (pattern?: string) => {\n const prefixes = resolveTenantPrefixes()\n const metaPattern = `${prefixes.keyPrefix}meta:*`\n const metaKeys = await base.keys(metaPattern)\n const originals: string[] = []\n for (const metaKey of metaKeys) {\n const metaValue = await base.get(metaKey, { returnExpired: true })\n if (!metaValue) continue\n const metadata = typeof metaValue === 'string' ? null : (isCacheMetadata(metaValue) ? metaValue : null)\n const original = typeof metaValue === 'string' ? metaValue : metadata?.key\n if (!original) continue\n if (pattern && !matchPattern(original, pattern)) continue\n originals.push(original)\n }\n return originals\n }\n\n const stats = async () => {\n const prefixes = resolveTenantPrefixes()\n const metaKeys = await base.keys(`${prefixes.keyPrefix}meta:*`)\n let size = 0\n let expired = 0\n const now = Date.now()\n for (const metaKey of metaKeys) {\n const metaValue = await base.get(metaKey, { returnExpired: true })\n if (!metaValue) continue\n const metadata = typeof metaValue === 'string' ? null : (isCacheMetadata(metaValue) ? metaValue : null)\n const original = typeof metaValue === 'string' ? metaValue : metadata?.key\n if (!original) continue\n size++\n const expiresAt = metadata?.expiresAt ?? null\n if (expiresAt !== null && expiresAt <= now) expired++\n }\n return { size, expired }\n }\n\n const cleanup = base.cleanup\n ? async () => normalizeDeletionCount(await base.cleanup!())\n : undefined\n\n const close = base.close\n ? async () => base.close!()\n : undefined\n\n return {\n get,\n set,\n has,\n delete: del,\n deleteByTags,\n clear,\n keys,\n stats,\n cleanup,\n close,\n }\n}\n\n/**\n * Cache service that provides a unified interface to different cache strategies\n * \n * Configuration via environment variables:\n * - CACHE_STRATEGY: 'memory' | 'redis' | 'sqlite' | 'jsonfile' (default: 'memory')\n * - CACHE_TTL: Default TTL in milliseconds (optional)\n * - CACHE_REDIS_URL: Redis connection URL (for redis strategy)\n * - CACHE_SQLITE_PATH: SQLite database file path (for sqlite strategy)\n * - CACHE_JSON_FILE_PATH: JSON file path (for jsonfile strategy)\n * \n * @example\n * const cache = createCacheService({ strategy: 'memory', defaultTtl: 60000 })\n * await cache.set('user:123', { name: 'John' }, { tags: ['users', 'user:123'] })\n * const user = await cache.get('user:123')\n * await cache.deleteByTags(['users']) // Invalidate all user-related cache\n */\nexport function createCacheService(options?: CacheServiceOptions): CacheStrategy {\n const envStrategy = isCacheStrategyName(process.env.CACHE_STRATEGY)\n ? process.env.CACHE_STRATEGY\n : undefined\n const strategyType: CacheStrategyName = options?.strategy ?? envStrategy ?? 'memory'\n\n const envTtl = process.env.CACHE_TTL\n const parsedEnvTtl = envTtl ? Number.parseInt(envTtl, 10) : undefined\n const defaultTtl = options?.defaultTtl ?? (typeof parsedEnvTtl === 'number' && Number.isFinite(parsedEnvTtl) ? parsedEnvTtl : undefined)\n\n const baseStrategy = createStrategyForType(strategyType, options, defaultTtl)\n const resilientStrategy = withDependencyFallback(baseStrategy, strategyType, defaultTtl)\n\n return createTenantAwareWrapper(resilientStrategy)\n}\n\n/**\n * CacheService class wrapper for DI integration\n * Provides the same interface as the functional API but as a class\n */\nexport class CacheService implements CacheStrategy {\n private strategy: CacheStrategy\n\n constructor(options?: CacheServiceOptions) {\n this.strategy = createCacheService(options)\n }\n\n async get(key: string, options?: CacheGetOptions): Promise<CacheValue | null> {\n return this.strategy.get(key, options)\n }\n\n async set(key: string, value: CacheValue, options?: CacheSetOptions): Promise<void> {\n return this.strategy.set(key, value, options)\n }\n\n async has(key: string): Promise<boolean> {\n return this.strategy.has(key)\n }\n\n async delete(key: string): Promise<boolean> {\n return this.strategy.delete(key)\n }\n\n async deleteByTags(tags: string[]): Promise<number> {\n return this.strategy.deleteByTags(tags)\n }\n\n async clear(): Promise<number> {\n return this.strategy.clear()\n }\n\n async keys(pattern?: string): Promise<string[]> {\n return this.strategy.keys(pattern)\n }\n\n async stats(): Promise<{ size: number; expired: number }> {\n return this.strategy.stats()\n }\n\n async cleanup(): Promise<number> {\n if (this.strategy.cleanup) {\n return this.strategy.cleanup()\n }\n return 0\n }\n\n async close(): Promise<void> {\n if (this.strategy.close) {\n return this.strategy.close()\n }\n }\n}\n\nfunction createStrategyForType(strategyType: CacheStrategyName, options?: CacheServiceOptions, defaultTtl?: number): CacheStrategy {\n switch (strategyType) {\n case 'redis':\n return createRedisStrategy(options?.redisUrl, { defaultTtl })\n case 'sqlite':\n return createSqliteStrategy(options?.sqlitePath, { defaultTtl })\n case 'jsonfile':\n return createJsonFileStrategy(options?.jsonFilePath, { defaultTtl })\n case 'memory':\n default:\n return createMemoryStrategy({ defaultTtl })\n }\n}\n\nfunction withDependencyFallback(strategy: CacheStrategy, strategyType: CacheStrategyName, defaultTtl?: number): CacheStrategy {\n if (strategyType === 'memory') return strategy\n\n let activeStrategy = strategy\n let fallbackStrategy: CacheStrategy | null = null\n let warned = false\n\n const ensureFallback = (error: CacheDependencyUnavailableError) => {\n if (!fallbackStrategy) {\n fallbackStrategy = createMemoryStrategy({ defaultTtl })\n }\n if (!warned) {\n const dependencyMessage = error.dependency\n ? ` (missing dependency: ${error.dependency})`\n : ''\n console.warn(`[cache] ${error.strategy} strategy unavailable${dependencyMessage}. Falling back to memory strategy.`)\n warned = true\n }\n activeStrategy = fallbackStrategy\n }\n\n const wrapMethod = <K extends keyof CacheStrategy>(method: K): CacheStrategy[K] => {\n const handler = async (...args: Parameters<NonNullable<CacheStrategy[K]>>) => {\n const fn = activeStrategy[method] as ((...methodArgs: Parameters<NonNullable<CacheStrategy[K]>>) => ReturnType<NonNullable<CacheStrategy[K]>>) | undefined\n if (!fn) {\n return undefined as Awaited<ReturnType<NonNullable<CacheStrategy[K]>>>\n }\n\n try {\n return await fn(...args)\n } catch (error) {\n if (error instanceof CacheDependencyUnavailableError) {\n ensureFallback(error)\n const fallbackFn = activeStrategy[method] as ((...methodArgs: Parameters<NonNullable<CacheStrategy[K]>>) => ReturnType<NonNullable<CacheStrategy[K]>>) | undefined\n if (!fallbackFn) {\n return undefined as Awaited<ReturnType<NonNullable<CacheStrategy[K]>>>\n }\n return fallbackFn(...args)\n }\n throw error\n }\n }\n\n return handler as CacheStrategy[K]\n }\n\n return {\n get: wrapMethod('get'),\n set: wrapMethod('set'),\n has: wrapMethod('has'),\n delete: wrapMethod('delete'),\n deleteByTags: wrapMethod('deleteByTags'),\n clear: wrapMethod('clear'),\n keys: wrapMethod('keys'),\n stats: wrapMethod('stats'),\n cleanup: typeof strategy.cleanup === 'function' ? wrapMethod('cleanup') : undefined,\n close: typeof strategy.close === 'function' ? wrapMethod('close') : undefined,\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["metaKey"]
7
+ }