@fjell/cache 4.6.22 → 4.7.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.
Files changed (52) hide show
  1. package/CACHE_IMPLEMENTATIONS.md +198 -0
  2. package/CONFIGURATION_GUIDE.md +167 -0
  3. package/CRITICAL_FIXES.md +68 -0
  4. package/README.md +506 -2
  5. package/debug_test2.js +0 -0
  6. package/debug_test3.js +0 -0
  7. package/dist/Cache.d.ts +4 -1
  8. package/dist/CacheContext.d.ts +27 -0
  9. package/dist/CacheMap.d.ts +89 -14
  10. package/dist/Instance.d.ts +4 -2
  11. package/dist/InstanceFactory.d.ts +3 -2
  12. package/dist/Operations.d.ts +2 -1
  13. package/dist/Options.d.ts +100 -0
  14. package/dist/browser/AsyncIndexDBCacheMap.d.ts +38 -0
  15. package/dist/browser/IndexDBCacheMap.d.ts +54 -0
  16. package/dist/browser/LocalStorageCacheMap.d.ts +43 -0
  17. package/dist/browser/SessionStorageCacheMap.d.ts +35 -0
  18. package/dist/eviction/EvictionStrategy.d.ts +50 -0
  19. package/dist/eviction/EvictionStrategyConfig.d.ts +97 -0
  20. package/dist/eviction/EvictionStrategyFactory.d.ts +12 -0
  21. package/dist/eviction/EvictionStrategyValidation.d.ts +36 -0
  22. package/dist/eviction/index.d.ts +9 -0
  23. package/dist/eviction/strategies/ARCEvictionStrategy.d.ts +68 -0
  24. package/dist/eviction/strategies/FIFOEvictionStrategy.d.ts +11 -0
  25. package/dist/eviction/strategies/LFUEvictionStrategy.d.ts +37 -0
  26. package/dist/eviction/strategies/LRUEvictionStrategy.d.ts +11 -0
  27. package/dist/eviction/strategies/MRUEvictionStrategy.d.ts +11 -0
  28. package/dist/eviction/strategies/RandomEvictionStrategy.d.ts +11 -0
  29. package/dist/eviction/strategies/TwoQueueEvictionStrategy.d.ts +53 -0
  30. package/dist/index.d.ts +24 -7
  31. package/dist/index.js +3879 -446
  32. package/dist/index.js.map +4 -4
  33. package/dist/memory/EnhancedMemoryCacheMap.d.ts +75 -0
  34. package/dist/memory/MemoryCacheMap.d.ts +33 -0
  35. package/dist/normalization.d.ts +20 -0
  36. package/dist/ops/action.d.ts +2 -3
  37. package/dist/ops/all.d.ts +2 -3
  38. package/dist/ops/allAction.d.ts +2 -3
  39. package/dist/ops/allFacet.d.ts +2 -3
  40. package/dist/ops/create.d.ts +2 -3
  41. package/dist/ops/facet.d.ts +2 -3
  42. package/dist/ops/find.d.ts +2 -3
  43. package/dist/ops/findOne.d.ts +2 -3
  44. package/dist/ops/get.d.ts +2 -3
  45. package/dist/ops/one.d.ts +2 -3
  46. package/dist/ops/remove.d.ts +2 -3
  47. package/dist/ops/reset.d.ts +2 -1
  48. package/dist/ops/retrieve.d.ts +2 -3
  49. package/dist/ops/set.d.ts +2 -2
  50. package/dist/ops/update.d.ts +2 -3
  51. package/dist/utils/CacheSize.d.ts +37 -0
  52. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,227 +1,38 @@
1
- // src/logger.ts
2
- import Logging from "@fjell/logging";
3
- var LibLogger = Logging.getLogger("@fjell/cache");
4
- var logger_default = LibLogger;
5
-
6
- // src/Aggregator.ts
7
- var logger = logger_default.get("ItemAggregator");
8
- var toCacheConfig = (config) => {
9
- let cacheConfig;
10
- if (config.optional === void 0) {
11
- cacheConfig = { cache: config, optional: false };
12
- } else {
13
- cacheConfig = config;
14
- }
15
- return cacheConfig;
16
- };
17
- var createAggregator = async (cache, { aggregates = {}, events = {} }) => {
18
- const populate = async (item) => {
19
- logger.default("populate", { item });
20
- for (const key in aggregates) {
21
- await populateAggregate(key, item);
22
- }
23
- for (const key in events) {
24
- await populateEvent(key, item);
25
- }
26
- logger.default("populate done", { item });
27
- return item;
28
- };
29
- const populateAggregate = async (key, item) => {
30
- logger.default("populate aggregate key", { key });
31
- const cacheConfig = toCacheConfig(aggregates[key]);
32
- if (item.refs === void 0) {
33
- if (cacheConfig.optional === false) {
34
- logger.error("Item does not have refs an is not optional " + JSON.stringify(item));
35
- throw new Error("Item does not have refs an is not optional " + JSON.stringify(item));
36
- } else {
37
- if (item.events && Object.prototype.hasOwnProperty.call(item.events, key)) {
38
- delete item.events[key];
39
- }
40
- }
41
- } else if (item.refs[key] === void 0) {
42
- if (cacheConfig.optional === false) {
43
- logger.error("Item does not have mandatory ref with key, not optional " + key + " " + JSON.stringify(item));
44
- throw new Error("Item does not have mandatory ref with key, not optional " + key + " " + JSON.stringify(item));
45
- } else {
46
- if (item.events && Object.prototype.hasOwnProperty.call(item.events, key)) {
47
- delete item.events[key];
48
- }
49
- }
50
- } else {
51
- const ref = item.refs[key];
52
- logger.default("AGG Retrieving Item in Populate", { key: ref });
53
- const [, newItem] = await cacheConfig.cache.operations.retrieve(ref);
54
- if (newItem) {
55
- if (item.aggs === void 0) {
56
- item.aggs = {};
57
- }
58
- item.aggs[key] = {
59
- key: ref,
60
- item: newItem
61
- };
62
- }
63
- }
64
- };
65
- const populateEvent = async (key, item) => {
66
- logger.default("populate event key", { key });
67
- const cacheConfig = toCacheConfig(events[key]);
68
- if (item.events === void 0) {
69
- throw new Error("Item does not have events " + JSON.stringify(item));
70
- } else if (item.events[key] === void 0) {
71
- if (cacheConfig.optional === false) {
72
- logger.error("Item does not have mandatory event with key " + key + " " + JSON.stringify(item));
73
- throw new Error("Item does not have mandatory event with key " + key + " " + JSON.stringify(item));
74
- }
75
- } else {
76
- const event = item.events[key];
77
- if (event.by === void 0) {
78
- logger.error(
79
- "populateEvent with an Event that does not have by",
80
- { event, ik: item.key, eventKey: key }
81
- );
82
- throw new Error("populateEvent with an Event that does not have by: " + JSON.stringify({ key, event }));
83
- }
84
- logger.default("EVENT Retrieving Item in Populate", { key: event.by });
85
- const [, newItem] = await cacheConfig.cache.operations.retrieve(event.by);
86
- if (newItem) {
87
- event.agg = newItem;
88
- }
89
- }
90
- };
91
- const all2 = async (query = {}, locations = []) => {
92
- logger.default("all", { query, locations });
93
- const [cacheMap, items] = await cache.operations.all(query, locations);
94
- const populatedItems = await Promise.all(items.map(async (item) => populate(item)));
95
- return [cacheMap, populatedItems];
96
- };
97
- const one2 = async (query = {}, locations = []) => {
98
- logger.default("one", { query, locations });
99
- const [cacheMap, item] = await cache.operations.one(query, locations);
100
- let populatedItem = null;
101
- if (item) {
102
- populatedItem = await populate(item);
103
- }
104
- return [cacheMap, populatedItem];
105
- };
106
- const action2 = async (key, action3, body = {}) => {
107
- logger.default("action", { key, action: action3, body });
108
- const [cacheMap, item] = await cache.operations.action(key, action3, body);
109
- const populatedItem = await populate(item);
110
- return [cacheMap, populatedItem];
111
- };
112
- const allAction2 = async (action3, body = {}, locations = []) => {
113
- logger.default("action", { action: action3, body, locations });
114
- const [cacheMap, items] = await cache.operations.allAction(action3, body, locations);
115
- const populatedItems = await Promise.all(items.map(async (item) => populate(item)));
116
- return [cacheMap, populatedItems];
117
- };
118
- const allFacet2 = async (facet3, params = {}, locations = []) => {
119
- logger.default("allFacet", { facet: facet3, params, locations });
120
- const [cacheMap, response] = await cache.operations.allFacet(facet3, params, locations);
121
- return [cacheMap, response];
122
- };
123
- const create2 = async (v, locations = []) => {
124
- logger.default("create", { v, locations });
125
- const [cacheMap, item] = await cache.operations.create(v, locations);
126
- const populatedItem = await populate(item);
127
- return [cacheMap, populatedItem];
128
- };
129
- const get2 = async (key) => {
130
- logger.default("get", { key });
131
- const [cacheMap, item] = await cache.operations.get(key);
132
- let populatedItem = null;
133
- if (item) {
134
- populatedItem = await populate(item);
135
- }
136
- return [cacheMap, populatedItem];
137
- };
138
- const retrieve2 = async (key) => {
139
- logger.default("retrieve", { key });
140
- const [cacheMap, item] = await cache.operations.retrieve(key);
141
- let populatedItem = null;
142
- if (item) {
143
- populatedItem = await populate(item);
144
- }
145
- return [cacheMap, populatedItem];
146
- };
147
- const remove2 = async (key) => {
148
- logger.default("remove", { key });
149
- const cacheMap = await cache.operations.remove(key);
150
- return cacheMap;
151
- };
152
- const update2 = async (key, v) => {
153
- logger.default("update", { key, v });
154
- const [cacheMap, item] = await cache.operations.update(key, v);
155
- const populatedItem = await populate(item);
156
- return [cacheMap, populatedItem];
157
- };
158
- const facet2 = async (key, facet3) => {
159
- logger.default("facet", { key, facet: facet3 });
160
- const [cacheMap, response] = await cache.operations.facet(key, facet3);
161
- return [cacheMap, response];
162
- };
163
- const find2 = async (finder, finderParams = {}, locations = []) => {
164
- logger.default("find", { finder, finderParams, locations });
165
- const [cacheMap, items] = await cache.operations.find(finder, finderParams, locations);
166
- const populatedItems = await Promise.all(items.map(async (item) => populate(item)));
167
- return [cacheMap, populatedItems];
168
- };
169
- const findOne2 = async (finder, finderParams = {}, locations = []) => {
170
- logger.default("find", { finder, finderParams, locations });
171
- const [cacheMap, item] = await cache.operations.findOne(finder, finderParams, locations);
172
- const populatedItem = await populate(item);
173
- return [cacheMap, populatedItem];
174
- };
175
- const set2 = async (key, v) => {
176
- logger.default("set", { key, v });
177
- const [cacheMap, item] = await cache.operations.set(key, v);
178
- const populatedItem = await populate(item);
179
- return [cacheMap, populatedItem];
180
- };
181
- const reset2 = async () => {
182
- const cacheMap = await cache.operations.reset();
183
- return cacheMap;
184
- };
1
+ // src/CacheContext.ts
2
+ var createCacheContext = (api, cacheMap, pkType, options) => {
185
3
  return {
186
- // Cache properties
187
- coordinate: cache.coordinate,
188
- registry: cache.registry,
189
- api: cache.api,
190
- cacheMap: cache.cacheMap,
191
- operations: cache.operations,
192
- // Cache operations exposed directly
193
- all: all2,
194
- one: one2,
195
- action: action2,
196
- allAction: allAction2,
197
- allFacet: allFacet2,
198
- create: create2,
199
- get: get2,
200
- retrieve: retrieve2,
201
- remove: remove2,
202
- update: update2,
203
- facet: facet2,
204
- find: find2,
205
- findOne: findOne2,
206
- reset: reset2,
207
- set: set2,
208
- // Aggregator-specific operations
209
- populate,
210
- populateAggregate,
211
- populateEvent
4
+ api,
5
+ cacheMap,
6
+ pkType,
7
+ options,
8
+ itemTtl: options.memoryConfig?.ttl || options.ttl,
9
+ queryTtl: options.memoryConfig?.ttl || options.ttl
212
10
  };
213
11
  };
214
12
 
215
- // src/CacheMap.ts
13
+ // src/ops/all.ts
216
14
  import {
217
- Dictionary,
218
- isComKey,
219
- isQueryMatch
15
+ validatePK
220
16
  } from "@fjell/core";
221
- var logger2 = logger_default.get("CacheMap");
17
+ import { NotFoundError } from "@fjell/http-api";
18
+
19
+ // src/normalization.ts
222
20
  var normalizeKeyValue = (value) => {
223
21
  return String(value);
224
22
  };
23
+ var deterministicStringify = (obj) => {
24
+ if (obj === null || typeof obj !== "object") {
25
+ return JSON.stringify(obj);
26
+ }
27
+ if (Array.isArray(obj)) {
28
+ return "[" + obj.map(deterministicStringify).join(",") + "]";
29
+ }
30
+ const sortedKeys = Object.keys(obj).sort();
31
+ const keyValuePairs = sortedKeys.map((key) => {
32
+ return JSON.stringify(key) + ":" + deterministicStringify(obj[key]);
33
+ });
34
+ return "{" + keyValuePairs.join(",") + "}";
35
+ };
225
36
  var createNormalizedHashFunction = () => {
226
37
  return (key) => {
227
38
  if (typeof key === "object" && key !== null) {
@@ -240,7 +51,7 @@ var createNormalizedHashFunction = () => {
240
51
  return locItem;
241
52
  });
242
53
  }
243
- return JSON.stringify(normalizedKey);
54
+ return deterministicStringify(normalizedKey);
244
55
  }
245
56
  return JSON.stringify(key);
246
57
  };
@@ -252,7 +63,7 @@ var isLocKeyArrayEqual = (a, b) => {
252
63
  for (let i = 0; i < a.length; i++) {
253
64
  const normalizedA = normalizeLocKeyItem(a[i]);
254
65
  const normalizedB = normalizeLocKeyItem(b[i]);
255
- if (JSON.stringify(normalizedA) !== JSON.stringify(normalizedB)) {
66
+ if (deterministicStringify(normalizedA) !== deterministicStringify(normalizedB)) {
256
67
  return false;
257
68
  }
258
69
  }
@@ -268,88 +79,92 @@ var normalizeLocKeyItem = (item) => {
268
79
  }
269
80
  return item;
270
81
  };
271
- var CacheMap = class _CacheMap extends Dictionary {
272
- types;
273
- normalizedHashFunction;
274
- constructor(types, map) {
275
- const hashFunc = createNormalizedHashFunction();
276
- super(map, hashFunc);
277
- this.types = types;
278
- this.normalizedHashFunction = hashFunc;
279
- }
280
- get(key) {
281
- logger2.trace("get", { key });
282
- const hashedKey = this.normalizedHashFunction(key);
283
- const entry = this.map[hashedKey];
284
- return entry && this.normalizedHashFunction(entry.originalKey) === this.normalizedHashFunction(key) ? entry.value : null;
285
- }
286
- includesKey(key) {
287
- const hashedKey = this.normalizedHashFunction(key);
288
- const entry = this.map[hashedKey];
289
- return entry ? this.normalizedHashFunction(entry.originalKey) === this.normalizedHashFunction(key) : false;
290
- }
291
- delete(key) {
292
- logger2.trace("delete", { key });
293
- const hashedKey = this.normalizedHashFunction(key);
294
- delete this.map[hashedKey];
295
- }
296
- allIn(locations) {
297
- if (locations.length === 0) {
298
- logger2.debug("Returning all items, LocKeys is empty");
299
- return this.values();
300
- } else {
301
- const locKeys = locations;
302
- logger2.debug("allIn", { locKeys, keys: this.keys().length });
303
- return this.keys().filter((key) => key && isComKey(key)).filter((key) => {
304
- const ComKey8 = key;
305
- logger2.debug("Comparing Location Keys", {
306
- locKeys,
307
- ComKey: ComKey8
308
- });
309
- return isLocKeyArrayEqual(locKeys, ComKey8.loc);
310
- }).map((key) => this.get(key));
311
- }
312
- }
313
- // TODO: Can we do case insensitive matching?
314
- contains(query, locations) {
315
- logger2.debug("contains", { query, locations });
316
- const items = this.allIn(locations);
317
- return items.some((item) => isQueryMatch(item, query));
318
- }
319
- queryIn(query, locations = []) {
320
- logger2.debug("queryIn", { query, locations });
321
- const items = this.allIn(locations);
322
- return items.filter((item) => isQueryMatch(item, query));
323
- }
324
- clone() {
325
- const clone = new _CacheMap(this.types);
326
- clone.map = this.map;
327
- clone.normalizedHashFunction = this.normalizedHashFunction;
328
- return clone;
329
- }
82
+ var createQueryHash = (pkType, query, locations) => {
83
+ const normalizedQuery = JSON.parse(JSON.stringify(query || {}));
84
+ const sortedQueryKeys = Object.keys(normalizedQuery).sort();
85
+ const sortedQuery = {};
86
+ sortedQueryKeys.forEach((key) => {
87
+ sortedQuery[key] = normalizedQuery[key];
88
+ });
89
+ const locationsArray = Array.isArray(locations) ? locations : [];
90
+ const normalizedLocations = locationsArray.map(normalizeLocKeyItem);
91
+ const hashInput = {
92
+ type: "query",
93
+ pkType,
94
+ query: sortedQuery,
95
+ locations: normalizedLocations
96
+ };
97
+ return deterministicStringify(hashInput);
330
98
  };
99
+ var createFinderHash = (finder, params, locations) => {
100
+ const normalizedParams = JSON.parse(JSON.stringify(params || {}));
101
+ const sortedParamKeys = Object.keys(normalizedParams).sort();
102
+ const sortedParams = {};
103
+ sortedParamKeys.forEach((key) => {
104
+ sortedParams[key] = normalizedParams[key];
105
+ });
106
+ const locationsArray = Array.isArray(locations) ? locations : [];
107
+ const normalizedLocations = locationsArray.map(normalizeLocKeyItem);
108
+ const hashInput = {
109
+ type: "finder",
110
+ finder,
111
+ params: sortedParams,
112
+ locations: normalizedLocations
113
+ };
114
+ return deterministicStringify(hashInput);
115
+ };
116
+
117
+ // src/logger.ts
118
+ import Logging from "@fjell/logging";
119
+ var LibLogger = Logging.getLogger("@fjell/cache");
120
+ var logger_default = LibLogger;
331
121
 
332
122
  // src/ops/all.ts
333
- import {
334
- validatePK
335
- } from "@fjell/core";
336
- import { NotFoundError } from "@fjell/http-api";
337
- var logger3 = logger_default.get("all");
338
- var all = async (api, cacheMap, pkType, query = {}, locations = []) => {
339
- logger3.default("all", { query, locations });
123
+ var logger = logger_default.get("all");
124
+ var all = async (query = {}, locations = [], context) => {
125
+ const { api, cacheMap, pkType, queryTtl } = context;
126
+ logger.default("all", { query, locations });
127
+ const queryHash = createQueryHash(pkType, query, locations);
128
+ logger.debug("Generated query hash for all", { queryHash });
129
+ const cachedItemKeys = cacheMap.getQueryResult(queryHash);
130
+ if (cachedItemKeys) {
131
+ logger.debug("Using cached query results", { cachedKeyCount: cachedItemKeys.length });
132
+ const cachedItems = [];
133
+ let allItemsAvailable = true;
134
+ for (const itemKey of cachedItemKeys) {
135
+ const item = cacheMap.get(itemKey);
136
+ if (item) {
137
+ cachedItems.push(item);
138
+ } else {
139
+ allItemsAvailable = false;
140
+ break;
141
+ }
142
+ }
143
+ if (allItemsAvailable) {
144
+ return [context, validatePK(cachedItems, pkType)];
145
+ } else {
146
+ logger.debug("Some cached items missing, invalidating query cache");
147
+ cacheMap.deleteQueryResult(queryHash);
148
+ }
149
+ }
340
150
  let ret = [];
341
151
  try {
342
152
  ret = await api.all(query, locations);
343
153
  ret.forEach((v) => {
344
154
  cacheMap.set(v.key, v);
345
155
  });
156
+ const itemKeys = ret.map((item) => item.key);
157
+ cacheMap.setQueryResult(queryHash, itemKeys, queryTtl);
158
+ logger.debug("Cached query result", { queryHash, itemKeyCount: itemKeys.length, ttl: queryTtl });
346
159
  } catch (e) {
347
160
  if (e instanceof NotFoundError) {
161
+ cacheMap.setQueryResult(queryHash, [], queryTtl);
162
+ logger.debug("Cached empty query result for not found", { queryHash });
348
163
  } else {
349
164
  throw e;
350
165
  }
351
166
  }
352
- return [cacheMap, validatePK(ret, pkType)];
167
+ return [context, validatePK(ret, pkType)];
353
168
  };
354
169
 
355
170
  // src/ops/one.ts
@@ -357,23 +172,47 @@ import {
357
172
  validatePK as validatePK2
358
173
  } from "@fjell/core";
359
174
  import { NotFoundError as NotFoundError2 } from "@fjell/http-api";
360
- var logger4 = logger_default.get("one");
361
- var one = async (api, cacheMap, pkType, query = {}, locations = []) => {
362
- logger4.default("one", { query, locations });
363
- let retItem = null;
364
- try {
175
+ var logger2 = logger_default.get("one");
176
+ var one = async (query = {}, locations = [], context) => {
177
+ const { api, cacheMap, pkType, queryTtl } = context;
178
+ logger2.default("one", { query, locations });
179
+ const queryHash = createQueryHash(pkType, query, locations);
180
+ logger2.debug("Generated query hash for one", { queryHash });
181
+ const cachedItemKeys = cacheMap.getQueryResult(queryHash);
182
+ if (cachedItemKeys) {
183
+ logger2.debug("Using cached query results", { cachedKeyCount: cachedItemKeys.length });
184
+ if (cachedItemKeys.length === 0) {
185
+ return [context, null];
186
+ }
187
+ const item = cacheMap.get(cachedItemKeys[0]);
188
+ if (item) {
189
+ return [context, validatePK2(item, pkType)];
190
+ } else {
191
+ logger2.debug("Cached item missing, invalidating query cache");
192
+ cacheMap.deleteQueryResult(queryHash);
193
+ }
194
+ }
195
+ let retItem = null;
196
+ try {
365
197
  retItem = await api.one(query, locations);
366
198
  if (retItem) {
367
199
  cacheMap.set(retItem.key, retItem);
200
+ cacheMap.setQueryResult(queryHash, [retItem.key], queryTtl);
201
+ logger2.debug("Cached query result", { queryHash, itemKey: retItem.key, ttl: queryTtl });
202
+ } else {
203
+ cacheMap.setQueryResult(queryHash, [], queryTtl);
204
+ logger2.debug("Cached empty query result", { queryHash, ttl: queryTtl });
368
205
  }
369
206
  } catch (e) {
370
207
  if (e instanceof NotFoundError2) {
208
+ cacheMap.setQueryResult(queryHash, [], queryTtl);
209
+ logger2.debug("Cached empty query result for not found", { queryHash });
371
210
  } else {
372
211
  throw e;
373
212
  }
374
213
  }
375
214
  return [
376
- cacheMap,
215
+ context,
377
216
  retItem ? validatePK2(retItem, pkType) : null
378
217
  ];
379
218
  };
@@ -382,12 +221,13 @@ var one = async (api, cacheMap, pkType, query = {}, locations = []) => {
382
221
  import {
383
222
  validatePK as validatePK3
384
223
  } from "@fjell/core";
385
- var logger5 = logger_default.get("create");
386
- var create = async (api, cacheMap, pkType, v, locations = []) => {
387
- logger5.default("create", { v, locations });
224
+ var logger3 = logger_default.get("create");
225
+ var create = async (v, locations = [], context) => {
226
+ const { api, cacheMap, pkType } = context;
227
+ logger3.default("create", { v, locations });
388
228
  const created = await api.create(v, locations);
389
229
  cacheMap.set(created.key, created);
390
- return [cacheMap, validatePK3(created, pkType)];
230
+ return [context, validatePK3(created, pkType)];
391
231
  };
392
232
 
393
233
  // src/ops/get.ts
@@ -395,25 +235,53 @@ import {
395
235
  isValidItemKey,
396
236
  validatePK as validatePK4
397
237
  } from "@fjell/core";
398
- var logger6 = logger_default.get("get");
399
- var get = async (api, cacheMap, pkType, key) => {
400
- logger6.default("get", { key });
238
+ var logger4 = logger_default.get("get");
239
+ var inFlightRequests = /* @__PURE__ */ new Map();
240
+ var keyToString = (key) => {
241
+ return JSON.stringify(key);
242
+ };
243
+ var get = async (key, context) => {
244
+ const { api, cacheMap, pkType, itemTtl } = context;
245
+ logger4.default("get", { key, itemTtl });
401
246
  if (!isValidItemKey(key)) {
402
- logger6.error("Key for Get is not a valid ItemKey: %j", key);
247
+ logger4.error("Key for Get is not a valid ItemKey: %j", key);
403
248
  throw new Error("Key for Get is not a valid ItemKey");
404
249
  }
250
+ if (typeof itemTtl === "number" && itemTtl > 0) {
251
+ const cachedItem = cacheMap.getWithTTL(key, itemTtl);
252
+ if (cachedItem) {
253
+ logger4.debug("Cache hit with TTL", { key, itemTtl });
254
+ return [context, validatePK4(cachedItem, pkType)];
255
+ }
256
+ logger4.debug("Cache miss or expired", { key, itemTtl });
257
+ }
405
258
  let ret;
259
+ const keyStr = keyToString(key);
406
260
  try {
407
- ret = await api.get(key);
261
+ let apiRequest = inFlightRequests.get(keyStr);
262
+ if (!apiRequest) {
263
+ apiRequest = api.get(key);
264
+ inFlightRequests.set(keyStr, apiRequest);
265
+ if (apiRequest && typeof apiRequest.finally === "function") {
266
+ apiRequest.finally(() => {
267
+ inFlightRequests.delete(keyStr);
268
+ });
269
+ } else {
270
+ inFlightRequests.delete(keyStr);
271
+ }
272
+ } else {
273
+ logger4.debug("Using in-flight request for key", { key });
274
+ }
275
+ ret = await apiRequest;
408
276
  if (ret) {
409
277
  cacheMap.set(ret.key, ret);
410
278
  }
411
279
  } catch (e) {
412
- logger6.error("Error getting item for key", { key, message: e.message, stack: e.stack });
280
+ logger4.error("Error getting item for key", { key, message: e.message, stack: e.stack });
413
281
  throw e;
414
282
  }
415
283
  return [
416
- cacheMap,
284
+ context,
417
285
  ret ? validatePK4(ret, pkType) : null
418
286
  ];
419
287
  };
@@ -423,24 +291,25 @@ import {
423
291
  isValidItemKey as isValidItemKey2,
424
292
  validatePK as validatePK5
425
293
  } from "@fjell/core";
426
- var logger7 = logger_default.get("retrieve");
427
- var retrieve = async (api, cacheMap, pkType, key) => {
428
- logger7.default("retrieve", { key });
294
+ var logger5 = logger_default.get("retrieve");
295
+ var retrieve = async (key, context) => {
296
+ const { cacheMap, pkType } = context;
297
+ logger5.default("retrieve", { key });
429
298
  if (!isValidItemKey2(key)) {
430
- logger7.error("Key for Retrieve is not a valid ItemKey: %j", key);
299
+ logger5.error("Key for Retrieve is not a valid ItemKey: %j", key);
431
300
  throw new Error("Key for Retrieve is not a valid ItemKey");
432
301
  }
433
302
  const containsItemKey = cacheMap.includesKey(key);
434
303
  let retrieved;
435
304
  if (containsItemKey) {
436
- logger7.default("Looking for Object in Cache", key);
305
+ logger5.default("Looking for Object in Cache", key);
437
306
  retrieved = cacheMap.get(key);
438
307
  } else {
439
- logger7.default("Object Not Found in Cache, Retrieving from Server API", { key });
440
- [, retrieved] = await get(api, cacheMap, pkType, key);
308
+ logger5.default("Object Not Found in Cache, Retrieving from Server API", { key });
309
+ [, retrieved] = await get(key, context);
441
310
  }
442
311
  const retValue = [
443
- containsItemKey ? null : cacheMap,
312
+ containsItemKey ? null : context,
444
313
  retrieved ? validatePK5(retrieved, pkType) : null
445
314
  ];
446
315
  return retValue;
@@ -450,21 +319,23 @@ var retrieve = async (api, cacheMap, pkType, key) => {
450
319
  import {
451
320
  isValidItemKey as isValidItemKey3
452
321
  } from "@fjell/core";
453
- var logger8 = logger_default.get("remove");
454
- var remove = async (api, cacheMap, key) => {
455
- logger8.default("remove", { key });
322
+ var logger6 = logger_default.get("remove");
323
+ var remove = async (key, context) => {
324
+ const { api, cacheMap } = context;
325
+ logger6.default("remove", { key });
456
326
  if (!isValidItemKey3(key)) {
457
- logger8.error("Key for Remove is not a valid ItemKey: %j", key);
327
+ logger6.error("Key for Remove is not a valid ItemKey: %j", key);
458
328
  throw new Error("Key for Remove is not a valid ItemKey");
459
329
  }
460
330
  try {
461
331
  await api.remove(key);
462
332
  cacheMap.delete(key);
333
+ logger6.debug("Successfully removed item from API and cache", { key });
463
334
  } catch (e) {
464
- logger8.error("Error deleting item", { error: e });
335
+ logger6.error("Error deleting item", { error: e });
465
336
  throw e;
466
337
  }
467
- return cacheMap;
338
+ return context;
468
339
  };
469
340
 
470
341
  // src/ops/update.ts
@@ -472,19 +343,23 @@ import {
472
343
  isValidItemKey as isValidItemKey4,
473
344
  validatePK as validatePK6
474
345
  } from "@fjell/core";
475
- var logger9 = logger_default.get("update");
476
- var update = async (api, cacheMap, pkType, key, v) => {
477
- logger9.default("update", { key, v });
346
+ var logger7 = logger_default.get("update");
347
+ var update = async (key, v, context) => {
348
+ const { api, cacheMap, pkType } = context;
349
+ logger7.default("update", { key, v });
478
350
  if (!isValidItemKey4(key)) {
479
- logger9.error("Key for Update is not a valid ItemKey: %j", key);
351
+ logger7.error("Key for Update is not a valid ItemKey: %j", key);
480
352
  throw new Error("Key for Update is not a valid ItemKey");
481
353
  }
354
+ logger7.debug("Invalidating item key before update", { key });
355
+ cacheMap.invalidateItemKeys([key]);
482
356
  try {
483
357
  const updated = await api.update(key, v);
358
+ logger7.debug("Caching update result", { updatedKey: updated.key });
484
359
  cacheMap.set(updated.key, updated);
485
- return [cacheMap, validatePK6(updated, pkType)];
360
+ return [context, validatePK6(updated, pkType)];
486
361
  } catch (e) {
487
- logger9.error("Error updating item", { error: e });
362
+ logger7.error("Error updating item", { error: e });
488
363
  throw e;
489
364
  }
490
365
  };
@@ -494,16 +369,20 @@ import {
494
369
  isValidItemKey as isValidItemKey5,
495
370
  validatePK as validatePK7
496
371
  } from "@fjell/core";
497
- var logger10 = logger_default.get("action");
498
- var action = async (api, cacheMap, pkType, key, action2, body = {}) => {
499
- logger10.default("action", { key, action: action2, body });
372
+ var logger8 = logger_default.get("action");
373
+ var action = async (key, action2, body = {}, context) => {
374
+ const { api, cacheMap, pkType } = context;
375
+ logger8.default("action", { key, action: action2, body });
500
376
  if (!isValidItemKey5(key)) {
501
- logger10.error("Key for Action is not a valid ItemKey: %j", key);
377
+ logger8.error("Key for Action is not a valid ItemKey: %j", key);
502
378
  throw new Error("Key for Action is not a valid ItemKey");
503
379
  }
380
+ logger8.debug("Invalidating item key before action", { key });
381
+ cacheMap.invalidateItemKeys([key]);
504
382
  const updated = await api.action(key, action2, body);
383
+ logger8.debug("Caching action result", { updatedKey: updated.key });
505
384
  cacheMap.set(updated.key, updated);
506
- return [cacheMap, validatePK7(updated, pkType)];
385
+ return [context, validatePK7(updated, pkType)];
507
386
  };
508
387
 
509
388
  // src/ops/allAction.ts
@@ -511,12 +390,16 @@ import {
511
390
  validatePK as validatePK8
512
391
  } from "@fjell/core";
513
392
  import { NotFoundError as NotFoundError3 } from "@fjell/http-api";
514
- var logger11 = logger_default.get("allAction");
515
- var allAction = async (api, cacheMap, pkType, action2, body = {}, locations = []) => {
516
- logger11.default("allAction", { action: action2, body, locations });
393
+ var logger9 = logger_default.get("allAction");
394
+ var allAction = async (action2, body = {}, locations = [], context) => {
395
+ const { api, cacheMap, pkType } = context;
396
+ logger9.default("allAction", { action: action2, body, locations });
397
+ logger9.debug("Invalidating location before allAction", { locations });
398
+ cacheMap.invalidateLocation(locations);
517
399
  let ret = [];
518
400
  try {
519
401
  ret = await api.allAction(action2, body, locations);
402
+ logger9.debug("Caching allAction results", { resultCount: ret.length });
520
403
  ret.forEach((v) => {
521
404
  cacheMap.set(v.key, v);
522
405
  });
@@ -526,49 +409,94 @@ var allAction = async (api, cacheMap, pkType, action2, body = {}, locations = []
526
409
  throw e;
527
410
  }
528
411
  }
529
- return [cacheMap, validatePK8(ret, pkType)];
412
+ return [context, validatePK8(ret, pkType)];
530
413
  };
531
414
 
532
415
  // src/ops/facet.ts
533
- var logger12 = logger_default.get("facet");
534
- var facet = async (api, cacheMap, key, facet2, params = {}) => {
535
- logger12.default("facet", { key, facet: facet2 });
416
+ var logger10 = logger_default.get("facet");
417
+ var facet = async (key, facet2, params = {}, context) => {
418
+ const { api } = context;
419
+ logger10.default("facet", { key, facet: facet2 });
536
420
  const ret = await api.facet(key, facet2, params);
537
- return [cacheMap, ret];
421
+ return ret;
538
422
  };
539
423
 
540
424
  // src/ops/allFacet.ts
541
- var logger13 = logger_default.get("allFacet");
542
- var allFacet = async (api, cacheMap, facet2, params = {}, locations = []) => {
543
- logger13.default("allFacet", { facet: facet2, params, locations });
425
+ var logger11 = logger_default.get("allFacet");
426
+ var allFacet = async (facet2, params = {}, locations = [], context) => {
427
+ const { api } = context;
428
+ logger11.default("allFacet", { facet: facet2, params, locations });
544
429
  const ret = await api.allFacet(facet2, params, locations);
545
- return [cacheMap, ret];
430
+ return ret;
546
431
  };
547
432
 
548
433
  // src/ops/find.ts
549
434
  import {
550
435
  validatePK as validatePK9
551
436
  } from "@fjell/core";
552
- var logger14 = logger_default.get("find");
553
- var find = async (api, cacheMap, pkType, finder, params = {}, locations = []) => {
554
- logger14.default("find", { finder, params, locations });
437
+ var logger12 = logger_default.get("find");
438
+ var find = async (finder, params = {}, locations = [], context) => {
439
+ const { api, cacheMap, pkType, queryTtl } = context;
440
+ logger12.default("find", { finder, params, locations });
441
+ const queryHash = createFinderHash(finder, params, locations);
442
+ logger12.debug("Generated query hash for find", { queryHash });
443
+ const cachedItemKeys = cacheMap.getQueryResult(queryHash);
444
+ if (cachedItemKeys) {
445
+ logger12.debug("Using cached query results", { cachedKeyCount: cachedItemKeys.length });
446
+ const cachedItems = [];
447
+ let allItemsAvailable = true;
448
+ for (const itemKey of cachedItemKeys) {
449
+ const item = cacheMap.get(itemKey);
450
+ if (item) {
451
+ cachedItems.push(item);
452
+ } else {
453
+ allItemsAvailable = false;
454
+ break;
455
+ }
456
+ }
457
+ if (allItemsAvailable) {
458
+ return [context, validatePK9(cachedItems, pkType)];
459
+ } else {
460
+ logger12.debug("Some cached items missing, invalidating query cache");
461
+ cacheMap.deleteQueryResult(queryHash);
462
+ }
463
+ }
555
464
  const ret = await api.find(finder, params, locations);
556
465
  ret.forEach((v) => {
557
466
  cacheMap.set(v.key, v);
558
467
  });
559
- return [cacheMap, validatePK9(ret, pkType)];
468
+ const itemKeys = ret.map((item) => item.key);
469
+ cacheMap.setQueryResult(queryHash, itemKeys, queryTtl);
470
+ logger12.debug("Cached query result", { queryHash, itemKeyCount: itemKeys.length, ttl: queryTtl });
471
+ return [context, validatePK9(ret, pkType)];
560
472
  };
561
473
 
562
474
  // src/ops/findOne.ts
563
475
  import {
564
476
  validatePK as validatePK10
565
477
  } from "@fjell/core";
566
- var logger15 = logger_default.get("findOne");
567
- var findOne = async (api, cacheMap, pkType, finder, finderParams = {}, locations = []) => {
568
- logger15.default("findOne", { finder, finderParams, locations });
478
+ var logger13 = logger_default.get("findOne");
479
+ var findOne = async (finder, finderParams = {}, locations = [], context) => {
480
+ const { api, cacheMap, pkType, queryTtl } = context;
481
+ logger13.default("findOne", { finder, finderParams, locations });
482
+ const queryHash = createFinderHash(finder, finderParams, locations);
483
+ logger13.debug("Generated query hash for findOne", { queryHash });
484
+ const cachedItemKeys = cacheMap.getQueryResult(queryHash);
485
+ if (cachedItemKeys && cachedItemKeys.length > 0) {
486
+ logger13.debug("Using cached query results", { cachedKeyCount: cachedItemKeys.length });
487
+ const item = cacheMap.get(cachedItemKeys[0]);
488
+ if (item) {
489
+ return [context, validatePK10(item, pkType)];
490
+ } else {
491
+ logger13.debug("Cached item missing, invalidating query cache");
492
+ cacheMap.deleteQueryResult(queryHash);
493
+ }
494
+ }
569
495
  const ret = await api.findOne(finder, finderParams, locations);
570
496
  cacheMap.set(ret.key, ret);
571
- return [cacheMap, validatePK10(ret, pkType)];
497
+ cacheMap.setQueryResult(queryHash, [ret.key], queryTtl);
498
+ logger13.debug("Cached query result", { queryHash, itemKey: ret.key, ttl: queryTtl });
499
+ return [context, validatePK10(ret, pkType)];
572
500
  };
573
501
 
574
502
  // src/ops/set.ts
@@ -577,7 +505,7 @@ import {
577
505
  isValidItemKey as isValidItemKey6,
578
506
  validatePK as validatePK11
579
507
  } from "@fjell/core";
580
- var logger16 = logger_default.get("set");
508
+ var logger14 = logger_default.get("set");
581
509
  var normalizeKeyValue2 = (value) => {
582
510
  return String(value);
583
511
  };
@@ -588,146 +516,3651 @@ var isItemKeyEqualNormalized = (a, b) => {
588
516
  };
589
517
  var normalizeKey = (key) => {
590
518
  if (typeof key === "object" && key !== null) {
591
- const normalizedKey = JSON.parse(JSON.stringify(key));
592
- if ("pk" in normalizedKey && normalizedKey.pk !== null) {
593
- normalizedKey.pk = normalizeKeyValue2(normalizedKey.pk);
519
+ let needsNormalization = false;
520
+ let normalizedKey = key;
521
+ if ("pk" in key && key.pk !== null && typeof key.pk !== "string") {
522
+ needsNormalization = true;
594
523
  }
595
- if ("lk" in normalizedKey && normalizedKey.lk !== null) {
596
- normalizedKey.lk = normalizeKeyValue2(normalizedKey.lk);
524
+ if ("lk" in key && key.lk !== null && typeof key.lk !== "string") {
525
+ needsNormalization = true;
597
526
  }
598
- if ("loc" in normalizedKey && Array.isArray(normalizedKey.loc)) {
599
- normalizedKey.loc = normalizedKey.loc.map((locItem) => {
600
- if (locItem && "lk" in locItem && locItem.lk !== null) {
601
- return { ...locItem, lk: normalizeKeyValue2(locItem.lk) };
527
+ if ("loc" in key && Array.isArray(key.loc)) {
528
+ for (const locItem of key.loc) {
529
+ if (locItem && "lk" in locItem && locItem.lk !== null && typeof locItem.lk !== "string") {
530
+ needsNormalization = true;
531
+ break;
602
532
  }
603
- return locItem;
604
- });
533
+ }
534
+ }
535
+ if (needsNormalization) {
536
+ normalizedKey = { ...key };
537
+ if ("pk" in normalizedKey && normalizedKey.pk !== null) {
538
+ normalizedKey.pk = normalizeKeyValue2(normalizedKey.pk);
539
+ }
540
+ if ("lk" in normalizedKey && normalizedKey.lk !== null) {
541
+ normalizedKey.lk = normalizeKeyValue2(normalizedKey.lk);
542
+ }
543
+ if ("loc" in normalizedKey && Array.isArray(normalizedKey.loc)) {
544
+ normalizedKey.loc = normalizedKey.loc.map((locItem) => {
545
+ if (locItem && "lk" in locItem && locItem.lk !== null && typeof locItem.lk !== "string") {
546
+ return { ...locItem, lk: normalizeKeyValue2(locItem.lk) };
547
+ }
548
+ return locItem;
549
+ });
550
+ }
605
551
  }
606
552
  return normalizedKey;
607
553
  }
608
554
  return key;
609
555
  };
610
- var set = async (cacheMap, pkType, key, v) => {
611
- logger16.default("set", { key, v });
556
+ var set = async (key, v, context) => {
557
+ const { cacheMap, pkType } = context;
558
+ logger14.default("set", { key, v });
612
559
  if (!isValidItemKey6(key)) {
613
- logger16.error("Key for Set is not a valid ItemKey: %j", key);
560
+ logger14.error("Key for Set is not a valid ItemKey: %j", key);
614
561
  throw new Error("Key for Set is not a valid ItemKey");
615
562
  }
616
563
  validatePK11(v, pkType);
617
564
  if (!isItemKeyEqualNormalized(key, v.key)) {
618
- logger16.error("Key does not match item key: %j != %j", key, v.key);
565
+ logger14.error("Key does not match item key: %j != %j", key, v.key);
619
566
  throw new Error("Key does not match item key");
620
567
  }
621
568
  cacheMap.set(key, v);
622
- return [cacheMap, validatePK11(v, pkType)];
569
+ return [context, validatePK11(v, pkType)];
623
570
  };
624
571
 
625
- // src/ops/reset.ts
626
- var reset = async (coordinate) => {
627
- const cacheMap = new CacheMap(coordinate.kta);
628
- return [cacheMap];
629
- };
572
+ // src/memory/MemoryCacheMap.ts
573
+ import {
574
+ isComKey,
575
+ isQueryMatch
576
+ } from "@fjell/core";
630
577
 
631
- // src/Operations.ts
632
- var createOperations = (api, coordinate, cacheMap, pkType) => {
633
- return {
634
- all: (query, locations) => all(api, cacheMap, pkType, query, locations),
635
- one: (query, locations) => one(api, cacheMap, pkType, query, locations),
636
- create: (item, locations) => create(api, cacheMap, pkType, item, locations),
637
- get: (key) => get(api, cacheMap, pkType, key),
638
- retrieve: (key) => retrieve(api, cacheMap, pkType, key),
639
- remove: (key) => remove(api, cacheMap, key),
640
- update: (key, item) => update(api, cacheMap, pkType, key, item),
641
- action: (key, actionName, body) => action(api, cacheMap, pkType, key, actionName, body),
642
- allAction: (actionName, body, locations) => allAction(api, cacheMap, pkType, actionName, body, locations),
643
- facet: (key, facetName, params) => facet(api, cacheMap, key, facetName, params),
644
- allFacet: (facetName, params, locations) => allFacet(api, cacheMap, facetName, params, locations),
645
- find: (finder, params, locations) => find(api, cacheMap, pkType, finder, params, locations),
646
- findOne: (finder, params, locations) => findOne(api, cacheMap, pkType, finder, params, locations),
647
- set: (key, item) => set(cacheMap, pkType, key, item),
648
- reset: () => reset(coordinate)
649
- };
578
+ // src/CacheMap.ts
579
+ var CacheMap = class {
580
+ types;
581
+ constructor(types) {
582
+ this.types = types;
583
+ }
650
584
  };
651
585
 
652
- // src/Cache.ts
653
- var logger17 = logger_default.get("Cache");
654
- var createCache = (api, coordinate, registry) => {
655
- logger17.debug("createCache", { coordinate, registry });
656
- const cacheMap = new CacheMap(coordinate.kta);
657
- const pkType = coordinate.kta[0];
658
- const operations = createOperations(api, coordinate, cacheMap, pkType);
659
- return {
660
- coordinate,
661
- registry,
662
- api,
663
- cacheMap,
664
- operations
665
- };
586
+ // src/memory/MemoryCacheMap.ts
587
+ var logger15 = logger_default.get("MemoryCacheMap");
588
+ var MemoryCacheMap = class _MemoryCacheMap extends CacheMap {
589
+ map = {};
590
+ normalizedHashFunction;
591
+ // Query result cache: maps query hash to cache entry with expiration
592
+ queryResultCache = {};
593
+ constructor(types, initialData) {
594
+ super(types);
595
+ this.normalizedHashFunction = createNormalizedHashFunction();
596
+ if (initialData) {
597
+ for (const [keyStr, value] of Object.entries(initialData)) {
598
+ try {
599
+ const key = JSON.parse(keyStr);
600
+ this.set(key, value);
601
+ } catch (error) {
602
+ logger15.error("Failed to parse initial data key", { keyStr, error });
603
+ }
604
+ }
605
+ }
606
+ }
607
+ get(key) {
608
+ logger15.trace("get", { key });
609
+ const hashedKey = this.normalizedHashFunction(key);
610
+ const entry = this.map[hashedKey];
611
+ return entry && this.normalizedHashFunction(entry.originalKey) === hashedKey ? entry.value : null;
612
+ }
613
+ getWithTTL(key, ttl) {
614
+ logger15.trace("getWithTTL", { key, ttl });
615
+ if (ttl === 0) {
616
+ return null;
617
+ }
618
+ const hashedKey = this.normalizedHashFunction(key);
619
+ const entry = this.map[hashedKey];
620
+ if (!entry || this.normalizedHashFunction(entry.originalKey) !== hashedKey) {
621
+ return null;
622
+ }
623
+ const now = Date.now();
624
+ const age = now - entry.timestamp;
625
+ if (age >= ttl) {
626
+ logger15.trace("Item expired, removing from cache", { key, age, ttl });
627
+ delete this.map[hashedKey];
628
+ return null;
629
+ }
630
+ return entry.value;
631
+ }
632
+ set(key, value) {
633
+ logger15.trace("set", { key, value });
634
+ const hashedKey = this.normalizedHashFunction(key);
635
+ this.map[hashedKey] = { originalKey: key, value, timestamp: Date.now() };
636
+ }
637
+ includesKey(key) {
638
+ const hashedKey = this.normalizedHashFunction(key);
639
+ const entry = this.map[hashedKey];
640
+ return !!entry && this.normalizedHashFunction(entry.originalKey) === hashedKey;
641
+ }
642
+ delete(key) {
643
+ logger15.trace("delete", { key });
644
+ const hashedKey = this.normalizedHashFunction(key);
645
+ delete this.map[hashedKey];
646
+ }
647
+ keys() {
648
+ return Object.values(this.map).map((entry) => entry.originalKey);
649
+ }
650
+ values() {
651
+ return Object.values(this.map).map((entry) => entry.value);
652
+ }
653
+ clear() {
654
+ this.map = {};
655
+ }
656
+ allIn(locations) {
657
+ const allValues = this.values();
658
+ if (locations.length === 0) {
659
+ logger15.debug("Returning all items, LocKeys is empty");
660
+ return allValues;
661
+ } else {
662
+ logger15.debug("allIn", { locations, count: allValues.length });
663
+ return allValues.filter((item) => {
664
+ const key = item.key;
665
+ if (key && isComKey(key)) {
666
+ const comKey = key;
667
+ return isLocKeyArrayEqual(locations, comKey.loc);
668
+ }
669
+ return false;
670
+ });
671
+ }
672
+ }
673
+ contains(query, locations) {
674
+ logger15.debug("contains", { query, locations });
675
+ const items = this.allIn(locations);
676
+ return items.some((item) => isQueryMatch(item, query));
677
+ }
678
+ queryIn(query, locations = []) {
679
+ logger15.debug("queryIn", { query, locations });
680
+ const items = this.allIn(locations);
681
+ return items.filter((item) => isQueryMatch(item, query));
682
+ }
683
+ clone() {
684
+ const clone = new _MemoryCacheMap(this.types);
685
+ for (const key of this.keys()) {
686
+ const value = this.get(key);
687
+ if (value) {
688
+ clone.set(key, value);
689
+ }
690
+ }
691
+ for (const [queryHash, entry] of Object.entries(this.queryResultCache)) {
692
+ clone.queryResultCache[queryHash] = {
693
+ itemKeys: [...entry.itemKeys],
694
+ // Shallow copy of the array
695
+ expiresAt: entry.expiresAt
696
+ };
697
+ }
698
+ return clone;
699
+ }
700
+ // Query result caching methods implementation
701
+ setQueryResult(queryHash, itemKeys, ttl) {
702
+ logger15.trace("setQueryResult", { queryHash, itemKeys, ttl });
703
+ const entry = {
704
+ itemKeys: [...itemKeys]
705
+ // Create a copy to avoid external mutations
706
+ };
707
+ if (ttl) {
708
+ entry.expiresAt = Date.now() + ttl;
709
+ }
710
+ this.queryResultCache[queryHash] = entry;
711
+ }
712
+ getQueryResult(queryHash) {
713
+ logger15.trace("getQueryResult", { queryHash });
714
+ const entry = this.queryResultCache[queryHash];
715
+ if (!entry) {
716
+ return null;
717
+ }
718
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
719
+ logger15.trace("Query result expired, removing", { queryHash, expiresAt: entry.expiresAt });
720
+ delete this.queryResultCache[queryHash];
721
+ return null;
722
+ }
723
+ return [...entry.itemKeys];
724
+ }
725
+ hasQueryResult(queryHash) {
726
+ const entry = this.queryResultCache[queryHash];
727
+ if (!entry) {
728
+ return false;
729
+ }
730
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
731
+ logger15.trace("Query result expired, removing", { queryHash, expiresAt: entry.expiresAt });
732
+ delete this.queryResultCache[queryHash];
733
+ return false;
734
+ }
735
+ return true;
736
+ }
737
+ deleteQueryResult(queryHash) {
738
+ logger15.trace("deleteQueryResult", { queryHash });
739
+ delete this.queryResultCache[queryHash];
740
+ }
741
+ invalidateItemKeys(keys) {
742
+ logger15.debug("invalidateItemKeys", { keys });
743
+ keys.forEach((key) => {
744
+ this.delete(key);
745
+ });
746
+ }
747
+ invalidateLocation(locations) {
748
+ logger15.debug("invalidateLocation", { locations });
749
+ if (locations.length === 0) {
750
+ const allKeys = this.keys();
751
+ const primaryKeys = allKeys.filter((key) => !isComKey(key));
752
+ this.invalidateItemKeys(primaryKeys);
753
+ } else {
754
+ const itemsInLocation = this.allIn(locations);
755
+ const keysToInvalidate = itemsInLocation.map((item) => item.key);
756
+ this.invalidateItemKeys(keysToInvalidate);
757
+ }
758
+ this.clearQueryResults();
759
+ }
760
+ clearQueryResults() {
761
+ logger15.trace("clearQueryResults");
762
+ this.queryResultCache = {};
763
+ }
666
764
  };
667
- var isCache = (cache) => {
668
- return cache !== null && typeof cache === "object" && "coordinate" in cache && "registry" in cache && "api" in cache && "cacheMap" in cache && "operations" in cache;
765
+
766
+ // src/memory/EnhancedMemoryCacheMap.ts
767
+ import {
768
+ isComKey as isComKey2,
769
+ isQueryMatch as isQueryMatch2
770
+ } from "@fjell/core";
771
+
772
+ // src/eviction/EvictionStrategy.ts
773
+ var EvictionStrategy = class {
669
774
  };
670
775
 
671
- // src/Instance.ts
672
- var logger18 = logger_default.get("Instance");
673
- var createInstance = (registry, coordinate, api) => {
674
- logger18.debug("createInstance", { coordinate, api, registry });
675
- return createCache(api, coordinate, registry);
776
+ // src/eviction/EvictionStrategyConfig.ts
777
+ var DEFAULT_LFU_CONFIG = {
778
+ type: "lfu",
779
+ decayFactor: 0.1,
780
+ decayInterval: 6e4,
781
+ // 1 minute
782
+ sketchWidth: 1024,
783
+ sketchDepth: 4,
784
+ useProbabilisticCounting: true,
785
+ minFrequencyThreshold: 1
676
786
  };
677
- var isInstance = (instance) => {
678
- return instance !== null && typeof instance === "object" && "coordinate" in instance && "registry" in instance && "api" in instance && "cacheMap" in instance && "operations" in instance;
787
+ var DEFAULT_ARC_CONFIG = {
788
+ type: "arc",
789
+ maxCacheSize: 1e3,
790
+ frequencyThreshold: 2,
791
+ useEnhancedFrequency: true,
792
+ frequencyDecayFactor: 0.05,
793
+ frequencyDecayInterval: 6e5,
794
+ // 10 minutes
795
+ useFrequencyWeightedSelection: true,
796
+ adaptiveLearningRate: 1
797
+ };
798
+ var DEFAULT_TWO_QUEUE_CONFIG = {
799
+ type: "2q",
800
+ maxCacheSize: 1e3,
801
+ useFrequencyPromotion: true,
802
+ promotionThreshold: 2,
803
+ hotQueueDecayFactor: 0.05,
804
+ hotQueueDecayInterval: 3e5,
805
+ // 5 minutes
806
+ useFrequencyWeightedLRU: true
679
807
  };
680
808
 
681
- // src/InstanceFactory.ts
682
- var logger19 = logger_default.get("InstanceFactory");
683
- var createInstanceFactory = (api) => {
684
- return (coordinate, context) => {
685
- logger19.debug("Creating cache instance", { coordinate, registry: context.registry, api });
686
- const cacheMap = new CacheMap(coordinate.kta);
687
- const pkType = coordinate.kta[0];
688
- const operations = createOperations(api, coordinate, cacheMap, pkType);
689
- return {
690
- coordinate,
691
- registry: context.registry,
692
- api,
693
- cacheMap,
694
- operations
809
+ // src/eviction/strategies/LRUEvictionStrategy.ts
810
+ var LRUEvictionStrategy = class extends EvictionStrategy {
811
+ selectForEviction(items) {
812
+ if (items.size === 0) return null;
813
+ let oldestKey = null;
814
+ let oldestTime = Infinity;
815
+ for (const [key, metadata] of items) {
816
+ if (metadata.lastAccessedAt < oldestTime) {
817
+ oldestTime = metadata.lastAccessedAt;
818
+ oldestKey = key;
819
+ }
820
+ }
821
+ return oldestKey;
822
+ }
823
+ onItemAccessed(_key, metadata) {
824
+ metadata.lastAccessedAt = Date.now();
825
+ metadata.accessCount++;
826
+ }
827
+ onItemAdded(_key, metadata) {
828
+ const now = Date.now();
829
+ metadata.addedAt = now;
830
+ metadata.lastAccessedAt = now;
831
+ metadata.accessCount = 1;
832
+ }
833
+ onItemRemoved() {
834
+ }
835
+ };
836
+
837
+ // src/eviction/EvictionStrategyValidation.ts
838
+ function validateNumberRange(value, min, max, fieldName) {
839
+ if (typeof value !== "number" || isNaN(value) || !isFinite(value)) {
840
+ throw new Error(`${fieldName} must be a finite number`);
841
+ }
842
+ if (value < min || value > max) {
843
+ throw new Error(`${fieldName} must be between ${min} and ${max}, got ${value}`);
844
+ }
845
+ }
846
+ function validatePositiveInteger(value, fieldName) {
847
+ if (typeof value !== "number" || isNaN(value) || !isFinite(value)) {
848
+ throw new Error(`${fieldName} must be a finite number`);
849
+ }
850
+ if (!Number.isInteger(value) || value <= 0) {
851
+ throw new Error(`${fieldName} must be a positive integer, got ${value}`);
852
+ }
853
+ }
854
+ function sanitizeLFUConfig(config) {
855
+ const sanitized = { ...config };
856
+ if (typeof sanitized.decayFactor === "number") {
857
+ if (sanitized.decayFactor < 0) {
858
+ console.warn(`decayFactor must be between 0 and 1, got ${sanitized.decayFactor}. Correcting to 0.`);
859
+ sanitized.decayFactor = 0;
860
+ } else if (sanitized.decayFactor > 1) {
861
+ console.warn(`decayFactor must be between 0 and 1, got ${sanitized.decayFactor}. Correcting to 1.`);
862
+ sanitized.decayFactor = 1;
863
+ }
864
+ }
865
+ if (typeof sanitized.decayInterval === "number" && sanitized.decayInterval <= 0) {
866
+ console.warn(`decayInterval must be positive, got ${sanitized.decayInterval}. Correcting to 300000.`);
867
+ sanitized.decayInterval = 3e5;
868
+ }
869
+ if (typeof sanitized.sketchWidth === "number") {
870
+ if (sanitized.sketchWidth <= 0) {
871
+ console.warn(`sketchWidth must be positive, got ${sanitized.sketchWidth}. Correcting to 1024.`);
872
+ sanitized.sketchWidth = 1024;
873
+ } else if (sanitized.sketchWidth < 16) {
874
+ console.warn(`sketchWidth should be at least 16 for optimal performance, got ${sanitized.sketchWidth}. Correcting to 16.`);
875
+ sanitized.sketchWidth = 16;
876
+ } else if (sanitized.sketchWidth > 65536) {
877
+ console.warn(`sketchWidth should not exceed 65536 for optimal performance, got ${sanitized.sketchWidth}. Correcting to 65536.`);
878
+ sanitized.sketchWidth = 65536;
879
+ }
880
+ }
881
+ if (typeof sanitized.sketchDepth === "number") {
882
+ if (sanitized.sketchDepth <= 0) {
883
+ console.warn(`sketchDepth must be positive, got ${sanitized.sketchDepth}. Correcting to 4.`);
884
+ sanitized.sketchDepth = 4;
885
+ } else if (sanitized.sketchDepth < 1) {
886
+ console.warn(`sketchDepth should be at least 1 for optimal accuracy, got ${sanitized.sketchDepth}. Correcting to 1.`);
887
+ sanitized.sketchDepth = 1;
888
+ } else if (sanitized.sketchDepth > 16) {
889
+ console.warn(`sketchDepth should not exceed 16 for optimal accuracy, got ${sanitized.sketchDepth}. Correcting to 16.`);
890
+ sanitized.sketchDepth = 16;
891
+ }
892
+ }
893
+ if (typeof sanitized.minFrequencyThreshold === "number" && sanitized.minFrequencyThreshold <= 0) {
894
+ console.warn(`minFrequencyThreshold must be positive, got ${sanitized.minFrequencyThreshold}. Correcting to 1.`);
895
+ sanitized.minFrequencyThreshold = 1;
896
+ }
897
+ return sanitized;
898
+ }
899
+ function validateLFUConfig(config) {
900
+ if (typeof config.decayFactor === "number") {
901
+ validateNumberRange(config.decayFactor, 0, 1, "decayFactor");
902
+ }
903
+ if (typeof config.decayInterval === "number") {
904
+ validatePositiveInteger(config.decayInterval, "decayInterval");
905
+ }
906
+ if (typeof config.sketchWidth === "number") {
907
+ validatePositiveInteger(config.sketchWidth, "sketchWidth");
908
+ if (config.sketchWidth < 16 || config.sketchWidth > 65536) {
909
+ throw new Error(`sketchWidth must be between 16 and 65536, got ${config.sketchWidth}`);
910
+ }
911
+ }
912
+ if (typeof config.sketchDepth === "number") {
913
+ validatePositiveInteger(config.sketchDepth, "sketchDepth");
914
+ if (config.sketchDepth < 1 || config.sketchDepth > 16) {
915
+ throw new Error(`sketchDepth must be between 1 and 16, got ${config.sketchDepth}`);
916
+ }
917
+ }
918
+ if (typeof config.minFrequencyThreshold === "number") {
919
+ validatePositiveInteger(config.minFrequencyThreshold, "minFrequencyThreshold");
920
+ }
921
+ }
922
+ function sanitizeARCConfig(config) {
923
+ const sanitized = { ...config };
924
+ if (typeof sanitized.maxCacheSize === "number" && sanitized.maxCacheSize <= 0) {
925
+ console.warn(`maxCacheSize must be positive, got ${sanitized.maxCacheSize}. Correcting to 1000.`);
926
+ sanitized.maxCacheSize = 1e3;
927
+ }
928
+ if (typeof sanitized.frequencyThreshold === "number" && sanitized.frequencyThreshold <= 0) {
929
+ console.warn(`frequencyThreshold must be positive, got ${sanitized.frequencyThreshold}. Correcting to 2.`);
930
+ sanitized.frequencyThreshold = 2;
931
+ }
932
+ if (typeof sanitized.frequencyDecayFactor === "number") {
933
+ if (sanitized.frequencyDecayFactor < 0) {
934
+ console.warn(`frequencyDecayFactor must be between 0 and 1, got ${sanitized.frequencyDecayFactor}. Correcting to 0.`);
935
+ sanitized.frequencyDecayFactor = 0;
936
+ } else if (sanitized.frequencyDecayFactor > 1) {
937
+ console.warn(`frequencyDecayFactor must be between 0 and 1, got ${sanitized.frequencyDecayFactor}. Correcting to 1.`);
938
+ sanitized.frequencyDecayFactor = 1;
939
+ }
940
+ }
941
+ if (typeof sanitized.frequencyDecayInterval === "number" && sanitized.frequencyDecayInterval <= 0) {
942
+ console.warn(`frequencyDecayInterval must be positive, got ${sanitized.frequencyDecayInterval}. Correcting to 60000.`);
943
+ sanitized.frequencyDecayInterval = 6e4;
944
+ }
945
+ if (typeof sanitized.adaptiveLearningRate === "number") {
946
+ if (sanitized.adaptiveLearningRate < 0) {
947
+ console.warn(`adaptiveLearningRate must be between 0 and 10, got ${sanitized.adaptiveLearningRate}. Correcting to 0.`);
948
+ sanitized.adaptiveLearningRate = 0;
949
+ } else if (sanitized.adaptiveLearningRate > 10) {
950
+ console.warn(`adaptiveLearningRate must be between 0 and 10, got ${sanitized.adaptiveLearningRate}. Correcting to 10.`);
951
+ sanitized.adaptiveLearningRate = 10;
952
+ }
953
+ }
954
+ return sanitized;
955
+ }
956
+ function validateARCConfig(config) {
957
+ if (typeof config.maxCacheSize === "number") {
958
+ validatePositiveInteger(config.maxCacheSize, "maxCacheSize");
959
+ }
960
+ if (typeof config.frequencyThreshold === "number") {
961
+ validatePositiveInteger(config.frequencyThreshold, "frequencyThreshold");
962
+ }
963
+ if (typeof config.frequencyDecayFactor === "number") {
964
+ validateNumberRange(config.frequencyDecayFactor, 0, 1, "frequencyDecayFactor");
965
+ }
966
+ if (typeof config.frequencyDecayInterval === "number") {
967
+ validatePositiveInteger(config.frequencyDecayInterval, "frequencyDecayInterval");
968
+ }
969
+ if (typeof config.adaptiveLearningRate === "number") {
970
+ validateNumberRange(config.adaptiveLearningRate, 0, 10, "adaptiveLearningRate");
971
+ }
972
+ }
973
+ function sanitizeTwoQueueConfig(config) {
974
+ const sanitized = { ...config };
975
+ if (typeof sanitized.maxCacheSize === "number" && sanitized.maxCacheSize <= 0) {
976
+ console.warn(`maxCacheSize must be positive, got ${sanitized.maxCacheSize}. Correcting to 1000.`);
977
+ sanitized.maxCacheSize = 1e3;
978
+ }
979
+ if (typeof sanitized.promotionThreshold === "number" && sanitized.promotionThreshold <= 0) {
980
+ console.warn(`promotionThreshold must be positive, got ${sanitized.promotionThreshold}. Correcting to 2.`);
981
+ sanitized.promotionThreshold = 2;
982
+ }
983
+ if (typeof sanitized.hotQueueDecayFactor === "number") {
984
+ if (sanitized.hotQueueDecayFactor < 0) {
985
+ console.warn(`hotQueueDecayFactor must be between 0 and 1, got ${sanitized.hotQueueDecayFactor}. Correcting to 0.`);
986
+ sanitized.hotQueueDecayFactor = 0;
987
+ } else if (sanitized.hotQueueDecayFactor > 1) {
988
+ console.warn(`hotQueueDecayFactor must be between 0 and 1, got ${sanitized.hotQueueDecayFactor}. Correcting to 1.`);
989
+ sanitized.hotQueueDecayFactor = 1;
990
+ }
991
+ }
992
+ if (typeof sanitized.hotQueueDecayInterval === "number" && sanitized.hotQueueDecayInterval <= 0) {
993
+ console.warn(`hotQueueDecayInterval must be positive, got ${sanitized.hotQueueDecayInterval}. Correcting to 300000.`);
994
+ sanitized.hotQueueDecayInterval = 3e5;
995
+ }
996
+ return sanitized;
997
+ }
998
+ function validateTwoQueueConfig(config) {
999
+ if (typeof config.maxCacheSize === "number") {
1000
+ validatePositiveInteger(config.maxCacheSize, "maxCacheSize");
1001
+ }
1002
+ if (typeof config.promotionThreshold === "number") {
1003
+ validatePositiveInteger(config.promotionThreshold, "promotionThreshold");
1004
+ }
1005
+ if (typeof config.hotQueueDecayFactor === "number") {
1006
+ validateNumberRange(config.hotQueueDecayFactor, 0, 1, "hotQueueDecayFactor");
1007
+ }
1008
+ if (typeof config.hotQueueDecayInterval === "number") {
1009
+ validatePositiveInteger(config.hotQueueDecayInterval, "hotQueueDecayInterval");
1010
+ }
1011
+ }
1012
+ function validateEvictionStrategyConfig(config) {
1013
+ if (!config || typeof config !== "object") {
1014
+ throw new Error("Configuration must be a non-null object");
1015
+ }
1016
+ if (!config.type) {
1017
+ throw new Error("Configuration must specify a type");
1018
+ }
1019
+ const validTypes = ["lfu", "lru", "fifo", "mru", "random", "arc", "2q"];
1020
+ if (!validTypes.includes(config.type)) {
1021
+ throw new Error(`Invalid eviction strategy type: ${config.type}. Must be one of: ${validTypes.join(", ")}`);
1022
+ }
1023
+ switch (config.type) {
1024
+ case "lfu":
1025
+ validateLFUConfig(config);
1026
+ break;
1027
+ case "arc":
1028
+ validateARCConfig(config);
1029
+ break;
1030
+ case "2q":
1031
+ validateTwoQueueConfig(config);
1032
+ break;
1033
+ case "lru":
1034
+ case "fifo":
1035
+ case "mru":
1036
+ case "random":
1037
+ break;
1038
+ default:
1039
+ throw new Error(`Unsupported eviction strategy type: ${config.type}`);
1040
+ }
1041
+ }
1042
+ function sanitizeConfigByType(config) {
1043
+ if (!config.type) {
1044
+ return config;
1045
+ }
1046
+ switch (config.type) {
1047
+ case "lfu":
1048
+ return sanitizeLFUConfig(config);
1049
+ case "arc":
1050
+ return sanitizeARCConfig(config);
1051
+ case "2q":
1052
+ return sanitizeTwoQueueConfig(config);
1053
+ case "lru":
1054
+ case "fifo":
1055
+ case "mru":
1056
+ case "random":
1057
+ return config;
1058
+ default:
1059
+ return config;
1060
+ }
1061
+ }
1062
+ function createValidatedConfig(baseConfig, userConfig) {
1063
+ const sanitizedUserConfig = sanitizeConfigByType(userConfig);
1064
+ const mergedConfig = { ...baseConfig, ...sanitizedUserConfig };
1065
+ validateEvictionStrategyConfig(mergedConfig);
1066
+ return mergedConfig;
1067
+ }
1068
+
1069
+ // src/eviction/strategies/LFUEvictionStrategy.ts
1070
+ function fnv1aHash(key, seed) {
1071
+ const FNV_OFFSET_BASIS = 2166136261;
1072
+ const FNV_PRIME = 16777619;
1073
+ let hash = (FNV_OFFSET_BASIS ^ seed) >>> 0;
1074
+ for (let i = 0; i < key.length; i++) {
1075
+ hash ^= key.charCodeAt(i);
1076
+ hash = hash * FNV_PRIME >>> 0;
1077
+ }
1078
+ hash ^= hash >>> 16;
1079
+ hash = hash * 2246822507 >>> 0;
1080
+ hash ^= hash >>> 13;
1081
+ hash = hash * 3266489909 >>> 0;
1082
+ hash ^= hash >>> 16;
1083
+ return hash >>> 0;
1084
+ }
1085
+ var CountMinSketch = class {
1086
+ sketches;
1087
+ width;
1088
+ depth;
1089
+ seeds;
1090
+ constructor(width = 1024, depth = 4) {
1091
+ this.width = width;
1092
+ this.depth = depth;
1093
+ this.sketches = Array(depth).fill(null).map(() => new Array(width).fill(0));
1094
+ this.seeds = Array(depth).fill(null).map(() => Math.floor(Math.random() * 1e6));
1095
+ }
1096
+ /**
1097
+ * Check if a number is a power of 2 for optimized bit masking
1098
+ */
1099
+ isPowerOfTwo(n) {
1100
+ return n > 0 && (n & n - 1) === 0;
1101
+ }
1102
+ /**
1103
+ * Increment the frequency count for a key
1104
+ */
1105
+ increment(key) {
1106
+ for (let i = 0; i < this.depth; i++) {
1107
+ const hash = fnv1aHash(key, this.seeds[i]);
1108
+ const index = this.isPowerOfTwo(this.width) ? hash & this.width - 1 : hash % this.width;
1109
+ this.sketches[i][index]++;
1110
+ }
1111
+ }
1112
+ /**
1113
+ * Estimate the frequency count for a key
1114
+ */
1115
+ estimate(key) {
1116
+ let minCount = Infinity;
1117
+ for (let i = 0; i < this.depth; i++) {
1118
+ const hash = fnv1aHash(key, this.seeds[i]);
1119
+ const index = this.isPowerOfTwo(this.width) ? hash & this.width - 1 : hash % this.width;
1120
+ minCount = Math.min(minCount, this.sketches[i][index]);
1121
+ }
1122
+ return minCount === Infinity ? 0 : minCount;
1123
+ }
1124
+ /**
1125
+ * Apply decay to all frequencies
1126
+ */
1127
+ decay(factor) {
1128
+ for (let i = 0; i < this.depth; i++) {
1129
+ for (let j = 0; j < this.width; j++) {
1130
+ this.sketches[i][j] = Math.floor(this.sketches[i][j] * (1 - factor));
1131
+ }
1132
+ }
1133
+ }
1134
+ /**
1135
+ * Reset all frequencies to zero
1136
+ */
1137
+ reset() {
1138
+ for (let i = 0; i < this.depth; i++) {
1139
+ for (let j = 0; j < this.width; j++) {
1140
+ this.sketches[i][j] = 0;
1141
+ }
1142
+ }
1143
+ }
1144
+ };
1145
+ var LFUEvictionStrategy = class extends EvictionStrategy {
1146
+ config;
1147
+ sketch;
1148
+ lastDecayTime;
1149
+ constructor(config = {}) {
1150
+ super();
1151
+ const defaultBackwardsCompatible = {
1152
+ useProbabilisticCounting: false,
1153
+ decayFactor: 0,
1154
+ decayInterval: Number.MAX_SAFE_INTEGER
695
1155
  };
696
- };
1156
+ const baseConfig = { ...DEFAULT_LFU_CONFIG, ...defaultBackwardsCompatible };
1157
+ this.config = createValidatedConfig(baseConfig, config);
1158
+ this.sketch = this.config.useProbabilisticCounting ? new CountMinSketch(this.config.sketchWidth, this.config.sketchDepth) : null;
1159
+ this.lastDecayTime = Date.now();
1160
+ }
1161
+ selectForEviction(items) {
1162
+ if (items.size === 0) return null;
1163
+ this.applyPeriodicDecay();
1164
+ let leastUsedKey = null;
1165
+ let lowestFrequency = Infinity;
1166
+ let oldestAccessTime = Infinity;
1167
+ for (const [key, metadata] of items) {
1168
+ const frequency = this.getEffectiveFrequency(key, metadata);
1169
+ if (frequency < lowestFrequency || frequency === lowestFrequency && metadata.lastAccessedAt < oldestAccessTime) {
1170
+ lowestFrequency = frequency;
1171
+ oldestAccessTime = metadata.lastAccessedAt;
1172
+ leastUsedKey = key;
1173
+ }
1174
+ }
1175
+ return leastUsedKey;
1176
+ }
1177
+ onItemAccessed(key, metadata) {
1178
+ const now = Date.now();
1179
+ metadata.lastAccessedAt = now;
1180
+ metadata.accessCount++;
1181
+ if (this.sketch) {
1182
+ this.sketch.increment(key);
1183
+ metadata.rawFrequency = this.sketch.estimate(key);
1184
+ } else {
1185
+ metadata.rawFrequency = metadata.accessCount;
1186
+ }
1187
+ if ((this.config.decayFactor ?? 0) > 0) {
1188
+ metadata.frequencyScore = this.calculateFrequencyScore(metadata, now);
1189
+ metadata.lastFrequencyUpdate = now;
1190
+ }
1191
+ }
1192
+ onItemAdded(key, metadata) {
1193
+ const now = Date.now();
1194
+ metadata.addedAt = now;
1195
+ metadata.lastAccessedAt = now;
1196
+ metadata.accessCount = 1;
1197
+ metadata.rawFrequency = 1;
1198
+ if ((this.config.decayFactor ?? 0) > 0) {
1199
+ metadata.frequencyScore = 1;
1200
+ metadata.lastFrequencyUpdate = now;
1201
+ }
1202
+ if (this.sketch) {
1203
+ this.sketch.increment(key);
1204
+ }
1205
+ }
1206
+ onItemRemoved() {
1207
+ }
1208
+ /**
1209
+ * Get the effective frequency for an item, applying real-time decay if needed
1210
+ */
1211
+ getEffectiveFrequency(_key, metadata) {
1212
+ if ((this.config.decayFactor ?? 0) === 0) {
1213
+ return metadata.rawFrequency || metadata.accessCount;
1214
+ }
1215
+ const now = Date.now();
1216
+ if (typeof metadata.frequencyScore === "number" && typeof metadata.lastFrequencyUpdate === "number") {
1217
+ const timeSinceUpdate = now - metadata.lastFrequencyUpdate;
1218
+ const decayAmount = timeSinceUpdate / (this.config.decayInterval ?? 6e4) * (this.config.decayFactor ?? 0.1);
1219
+ return Math.max(this.config.minFrequencyThreshold ?? 1, metadata.frequencyScore * (1 - decayAmount));
1220
+ }
1221
+ return metadata.rawFrequency || metadata.accessCount;
1222
+ }
1223
+ /**
1224
+ * Calculate frequency score with decay applied
1225
+ */
1226
+ calculateFrequencyScore(metadata, currentTime) {
1227
+ const rawFreq = metadata.rawFrequency || metadata.accessCount;
1228
+ if (typeof metadata.lastFrequencyUpdate !== "number") {
1229
+ return rawFreq;
1230
+ }
1231
+ const timeSinceUpdate = currentTime - metadata.lastFrequencyUpdate;
1232
+ const decayAmount = timeSinceUpdate / (this.config.decayInterval ?? 6e4) * (this.config.decayFactor ?? 0.1);
1233
+ const previousScore = metadata.frequencyScore || rawFreq;
1234
+ const decayedScore = previousScore * (1 - decayAmount);
1235
+ return Math.max(this.config.minFrequencyThreshold ?? 1, decayedScore + 1);
1236
+ }
1237
+ /**
1238
+ * Apply periodic decay to the frequency sketch and metadata
1239
+ */
1240
+ applyPeriodicDecay() {
1241
+ if ((this.config.decayFactor ?? 0) === 0) return;
1242
+ const now = Date.now();
1243
+ const timeSinceDecay = now - this.lastDecayTime;
1244
+ if (timeSinceDecay >= (this.config.decayInterval ?? 6e4)) {
1245
+ if (this.sketch) {
1246
+ this.sketch.decay(this.config.decayFactor ?? 0.1);
1247
+ }
1248
+ this.lastDecayTime = now;
1249
+ }
1250
+ }
1251
+ /**
1252
+ * Get configuration for this strategy
1253
+ */
1254
+ getConfig() {
1255
+ return { ...this.config };
1256
+ }
1257
+ /**
1258
+ * Reset frequency tracking (useful for testing or cache clearing)
1259
+ */
1260
+ reset() {
1261
+ if (this.sketch) {
1262
+ this.sketch.reset();
1263
+ }
1264
+ this.lastDecayTime = Date.now();
1265
+ }
697
1266
  };
698
1267
 
699
- // src/Registry.ts
700
- import {
701
- createRegistry as createBaseRegistry
702
- } from "@fjell/registry";
703
- var logger20 = logger_default.get("Registry");
704
- var createRegistryFactory = () => {
705
- return (type, registryHub) => {
706
- if (type !== "cache") {
707
- throw new Error(`Cache registry factory can only create 'cache' type registries, got: ${type}`);
708
- }
709
- logger20.debug("Creating cache registry", { type, registryHub });
710
- const baseRegistry = createBaseRegistry(type, registryHub);
711
- return baseRegistry;
712
- };
1268
+ // src/eviction/strategies/FIFOEvictionStrategy.ts
1269
+ var FIFOEvictionStrategy = class extends EvictionStrategy {
1270
+ selectForEviction(items) {
1271
+ if (items.size === 0) return null;
1272
+ let oldestKey = null;
1273
+ let oldestTime = Infinity;
1274
+ for (const [key, metadata] of items) {
1275
+ if (metadata.addedAt < oldestTime) {
1276
+ oldestTime = metadata.addedAt;
1277
+ oldestKey = key;
1278
+ }
1279
+ }
1280
+ return oldestKey;
1281
+ }
1282
+ onItemAccessed(_key, metadata) {
1283
+ metadata.lastAccessedAt = Date.now();
1284
+ metadata.accessCount++;
1285
+ }
1286
+ onItemAdded(_key, metadata) {
1287
+ const now = Date.now();
1288
+ metadata.addedAt = now;
1289
+ metadata.lastAccessedAt = now;
1290
+ metadata.accessCount = 1;
1291
+ }
1292
+ onItemRemoved() {
1293
+ }
713
1294
  };
714
- var createRegistry = (registryHub) => {
715
- const baseRegistry = createBaseRegistry("cache", registryHub);
716
- return {
717
- ...baseRegistry
1295
+
1296
+ // src/eviction/strategies/MRUEvictionStrategy.ts
1297
+ var MRUEvictionStrategy = class extends EvictionStrategy {
1298
+ selectForEviction(items) {
1299
+ if (items.size === 0) return null;
1300
+ let newestKey = null;
1301
+ let newestTime = -1;
1302
+ for (const [key, metadata] of items) {
1303
+ if (metadata.lastAccessedAt > newestTime) {
1304
+ newestTime = metadata.lastAccessedAt;
1305
+ newestKey = key;
1306
+ }
1307
+ }
1308
+ return newestKey;
1309
+ }
1310
+ onItemAccessed(_key, metadata) {
1311
+ metadata.lastAccessedAt = Date.now();
1312
+ metadata.accessCount++;
1313
+ }
1314
+ onItemAdded(_key, metadata) {
1315
+ const now = Date.now();
1316
+ metadata.addedAt = now;
1317
+ metadata.lastAccessedAt = now;
1318
+ metadata.accessCount = 1;
1319
+ }
1320
+ onItemRemoved() {
1321
+ }
1322
+ };
1323
+
1324
+ // src/eviction/strategies/RandomEvictionStrategy.ts
1325
+ var RandomEvictionStrategy = class extends EvictionStrategy {
1326
+ selectForEviction(items) {
1327
+ if (items.size === 0) return null;
1328
+ const keys = Array.from(items.keys());
1329
+ const randomIndex = Math.floor(Math.random() * keys.length);
1330
+ return keys[randomIndex];
1331
+ }
1332
+ onItemAccessed(_key, metadata) {
1333
+ metadata.lastAccessedAt = Date.now();
1334
+ metadata.accessCount++;
1335
+ }
1336
+ onItemAdded(_key, metadata) {
1337
+ const now = Date.now();
1338
+ metadata.addedAt = now;
1339
+ metadata.lastAccessedAt = now;
1340
+ metadata.accessCount = 1;
1341
+ }
1342
+ onItemRemoved() {
1343
+ }
1344
+ };
1345
+
1346
+ // src/eviction/strategies/ARCEvictionStrategy.ts
1347
+ var ARCEvictionStrategy = class extends EvictionStrategy {
1348
+ recentGhosts = /* @__PURE__ */ new Set();
1349
+ // T1 ghost entries
1350
+ frequentGhosts = /* @__PURE__ */ new Set();
1351
+ // T2 ghost entries
1352
+ targetRecentSize = 0;
1353
+ // Target size for T1 (recent entries)
1354
+ config;
1355
+ maxGhostSize;
1356
+ lastDecayTime;
1357
+ constructor(maxCacheSize = 1e3, config = {}) {
1358
+ super();
1359
+ const baseConfig = { ...DEFAULT_ARC_CONFIG, maxCacheSize };
1360
+ this.config = createValidatedConfig(baseConfig, config);
1361
+ this.maxGhostSize = this.config.maxCacheSize;
1362
+ this.lastDecayTime = Date.now();
1363
+ }
1364
+ selectForEviction(items) {
1365
+ if (items.size === 0) return null;
1366
+ this.applyPeriodicDecay(items);
1367
+ const recentItems = /* @__PURE__ */ new Map();
1368
+ const frequentItems = /* @__PURE__ */ new Map();
1369
+ for (const [key, metadata] of items) {
1370
+ if (this.isFrequentItem(metadata)) {
1371
+ frequentItems.set(key, metadata);
1372
+ } else {
1373
+ recentItems.set(key, metadata);
1374
+ }
1375
+ }
1376
+ if (recentItems.size > this.targetRecentSize && recentItems.size > 0) {
1377
+ return this.config.useFrequencyWeightedSelection ? this.selectFrequencyWeightedFromItems(recentItems, "recent") : this.selectLRUFromItems(recentItems);
1378
+ } else if (frequentItems.size > 0) {
1379
+ return this.config.useFrequencyWeightedSelection ? this.selectFrequencyWeightedFromItems(frequentItems, "frequent") : this.selectLRUFromItems(frequentItems);
1380
+ }
1381
+ return this.config.useFrequencyWeightedSelection ? this.selectFrequencyWeightedFromItems(items, "fallback") : this.selectLRUFromItems(items);
1382
+ }
1383
+ selectLRUFromItems(items) {
1384
+ let oldestKey = null;
1385
+ let oldestTime = Infinity;
1386
+ for (const [key, metadata] of items) {
1387
+ if (metadata.lastAccessedAt < oldestTime) {
1388
+ oldestTime = metadata.lastAccessedAt;
1389
+ oldestKey = key;
1390
+ }
1391
+ }
1392
+ return oldestKey;
1393
+ }
1394
+ onItemAccessed(key, metadata) {
1395
+ const now = Date.now();
1396
+ metadata.lastAccessedAt = now;
1397
+ metadata.accessCount++;
1398
+ metadata.rawFrequency = metadata.accessCount;
1399
+ if (this.config.useEnhancedFrequency && (this.config.frequencyDecayFactor ?? 0) > 0) {
1400
+ metadata.frequencyScore = this.calculateFrequencyScore(metadata, now);
1401
+ metadata.lastFrequencyUpdate = now;
1402
+ }
1403
+ const learningRate = this.config.adaptiveLearningRate ?? 1;
1404
+ if (this.recentGhosts.has(key)) {
1405
+ const adjustment = Math.ceil(learningRate);
1406
+ this.targetRecentSize = Math.min(this.targetRecentSize + adjustment, this.maxGhostSize);
1407
+ this.recentGhosts.delete(key);
1408
+ } else if (this.frequentGhosts.has(key)) {
1409
+ const adjustment = Math.ceil(learningRate);
1410
+ this.targetRecentSize = Math.max(this.targetRecentSize - adjustment, 0);
1411
+ this.frequentGhosts.delete(key);
1412
+ }
1413
+ }
1414
+ onItemAdded(key, metadata) {
1415
+ const now = Date.now();
1416
+ metadata.addedAt = now;
1417
+ metadata.lastAccessedAt = now;
1418
+ metadata.accessCount = 1;
1419
+ metadata.rawFrequency = 1;
1420
+ if (this.config.useEnhancedFrequency && (this.config.frequencyDecayFactor ?? 0) > 0) {
1421
+ metadata.frequencyScore = 1;
1422
+ metadata.lastFrequencyUpdate = now;
1423
+ }
1424
+ }
1425
+ onItemRemoved(key) {
1426
+ this.addToRecentGhosts(key);
1427
+ this.cleanupGhostLists();
1428
+ }
1429
+ /**
1430
+ * Add key to recent ghost list with proper size management
1431
+ */
1432
+ addToRecentGhosts(key) {
1433
+ this.frequentGhosts.delete(key);
1434
+ this.recentGhosts.add(key);
1435
+ while (this.recentGhosts.size > this.maxGhostSize) {
1436
+ const firstKey = this.recentGhosts.values().next().value;
1437
+ if (firstKey) {
1438
+ this.recentGhosts.delete(firstKey);
1439
+ }
1440
+ }
1441
+ }
1442
+ /**
1443
+ * Add key to frequent ghost list with proper size management
1444
+ */
1445
+ addToFrequentGhosts(key) {
1446
+ this.recentGhosts.delete(key);
1447
+ this.frequentGhosts.add(key);
1448
+ while (this.frequentGhosts.size > this.maxGhostSize) {
1449
+ const firstKey = this.frequentGhosts.values().next().value;
1450
+ if (firstKey) {
1451
+ this.frequentGhosts.delete(firstKey);
1452
+ }
1453
+ }
1454
+ }
1455
+ /**
1456
+ * Cleanup ghost lists to prevent memory leaks
1457
+ */
1458
+ cleanupGhostLists() {
1459
+ while (this.recentGhosts.size > this.maxGhostSize) {
1460
+ const firstKey = this.recentGhosts.values().next().value;
1461
+ if (firstKey) {
1462
+ this.recentGhosts.delete(firstKey);
1463
+ } else {
1464
+ break;
1465
+ }
1466
+ }
1467
+ while (this.frequentGhosts.size > this.maxGhostSize) {
1468
+ const firstKey = this.frequentGhosts.values().next().value;
1469
+ if (firstKey) {
1470
+ this.frequentGhosts.delete(firstKey);
1471
+ } else {
1472
+ break;
1473
+ }
1474
+ }
1475
+ }
1476
+ /**
1477
+ * Determine if an item should be classified as frequent vs recent
1478
+ */
1479
+ isFrequentItem(metadata) {
1480
+ if (!this.config.useEnhancedFrequency) {
1481
+ return metadata.accessCount > 1;
1482
+ }
1483
+ const frequency = this.getEffectiveFrequency(metadata);
1484
+ return frequency >= (this.config.frequencyThreshold ?? 2);
1485
+ }
1486
+ /**
1487
+ * Get effective frequency for an item, applying decay if enabled
1488
+ */
1489
+ getEffectiveFrequency(metadata) {
1490
+ if (!this.config.useEnhancedFrequency || (this.config.frequencyDecayFactor ?? 0) === 0) {
1491
+ return metadata.rawFrequency || metadata.accessCount;
1492
+ }
1493
+ const now = Date.now();
1494
+ if (typeof metadata.frequencyScore === "number" && typeof metadata.lastFrequencyUpdate === "number") {
1495
+ const timeSinceUpdate = now - metadata.lastFrequencyUpdate;
1496
+ const decayAmount = timeSinceUpdate / (this.config.frequencyDecayInterval ?? 6e5) * (this.config.frequencyDecayFactor ?? 0.05);
1497
+ return Math.max(1, metadata.frequencyScore * (1 - decayAmount));
1498
+ }
1499
+ return metadata.rawFrequency || metadata.accessCount;
1500
+ }
1501
+ /**
1502
+ * Calculate frequency score with decay applied
1503
+ */
1504
+ calculateFrequencyScore(metadata, currentTime) {
1505
+ const rawFreq = metadata.rawFrequency || metadata.accessCount;
1506
+ if (typeof metadata.lastFrequencyUpdate !== "number") {
1507
+ return rawFreq;
1508
+ }
1509
+ const timeSinceUpdate = currentTime - metadata.lastFrequencyUpdate;
1510
+ const decayAmount = timeSinceUpdate / (this.config.frequencyDecayInterval ?? 6e5) * (this.config.frequencyDecayFactor ?? 0.05);
1511
+ const previousScore = metadata.frequencyScore || rawFreq;
1512
+ const decayedScore = previousScore * (1 - decayAmount);
1513
+ return Math.max(1, decayedScore + 1);
1514
+ }
1515
+ /**
1516
+ * Select eviction candidate using frequency-weighted approach
1517
+ */
1518
+ selectFrequencyWeightedFromItems(items, context) {
1519
+ let bestKey = null;
1520
+ let bestScore = Infinity;
1521
+ for (const [key, metadata] of items) {
1522
+ const frequency = this.getEffectiveFrequency(metadata);
1523
+ const timeFactor = Date.now() - metadata.lastAccessedAt;
1524
+ let score;
1525
+ if (context === "recent") {
1526
+ score = timeFactor + 1e3 / Math.max(1, frequency);
1527
+ } else if (context === "frequent") {
1528
+ score = timeFactor / 1e3 + 10 / Math.max(1, frequency);
1529
+ } else {
1530
+ score = timeFactor / 1e3 / Math.max(1, frequency);
1531
+ }
1532
+ if (score < bestScore) {
1533
+ bestScore = score;
1534
+ bestKey = key;
1535
+ }
1536
+ }
1537
+ return bestKey || (items.size > 0 ? items.keys().next().value ?? null : null);
1538
+ }
1539
+ /**
1540
+ * Apply periodic decay to frequency scores
1541
+ */
1542
+ applyPeriodicDecay(items) {
1543
+ if (!this.config.useEnhancedFrequency || (this.config.frequencyDecayFactor ?? 0) === 0) return;
1544
+ const now = Date.now();
1545
+ const timeSinceDecay = now - this.lastDecayTime;
1546
+ if (timeSinceDecay >= (this.config.frequencyDecayInterval ?? 6e5)) {
1547
+ if (items.size > 0) {
1548
+ for (const metadata of items.values()) {
1549
+ if (typeof metadata.frequencyScore === "number") {
1550
+ const decayAmount = this.config.frequencyDecayFactor ?? 0.05;
1551
+ metadata.frequencyScore = Math.max(1, metadata.frequencyScore * (1 - decayAmount));
1552
+ }
1553
+ }
1554
+ this.lastDecayTime = now;
1555
+ }
1556
+ }
1557
+ }
1558
+ /**
1559
+ * Get configuration for this strategy
1560
+ */
1561
+ getConfig() {
1562
+ return { ...this.config };
1563
+ }
1564
+ /**
1565
+ * Reset internal state (useful for testing)
1566
+ */
1567
+ reset() {
1568
+ this.recentGhosts.clear();
1569
+ this.frequentGhosts.clear();
1570
+ this.targetRecentSize = 0;
1571
+ this.lastDecayTime = Date.now();
1572
+ }
1573
+ /**
1574
+ * Get current adaptive state for monitoring/debugging
1575
+ */
1576
+ getAdaptiveState() {
1577
+ return {
1578
+ targetRecentSize: this.targetRecentSize,
1579
+ recentGhostSize: this.recentGhosts.size,
1580
+ frequentGhostSize: this.frequentGhosts.size
1581
+ };
1582
+ }
1583
+ };
1584
+
1585
+ // src/eviction/strategies/TwoQueueEvictionStrategy.ts
1586
+ var TwoQueueEvictionStrategy = class extends EvictionStrategy {
1587
+ recentQueue = [];
1588
+ // A1 queue for recent items
1589
+ hotQueue = [];
1590
+ // Am queue for hot items
1591
+ ghostQueue = /* @__PURE__ */ new Set();
1592
+ // A1out ghost queue
1593
+ config;
1594
+ maxRecentSize;
1595
+ maxGhostSize;
1596
+ lastDecayTime;
1597
+ constructor(maxCacheSize = 1e3, config = {}) {
1598
+ super();
1599
+ const baseConfig = { ...DEFAULT_TWO_QUEUE_CONFIG, maxCacheSize };
1600
+ this.config = createValidatedConfig(baseConfig, config);
1601
+ this.maxRecentSize = Math.max(1, Math.floor(this.config.maxCacheSize * 0.25));
1602
+ this.maxGhostSize = this.config.maxCacheSize;
1603
+ this.lastDecayTime = Date.now();
1604
+ }
1605
+ selectForEviction(items) {
1606
+ if (items.size === 0) return null;
1607
+ this.applyPeriodicDecay(items);
1608
+ for (let i = this.recentQueue.length - 1; i >= 0; i--) {
1609
+ const key = this.recentQueue[i];
1610
+ if (items.has(key)) {
1611
+ return key;
1612
+ }
1613
+ }
1614
+ if (this.config.useFrequencyWeightedLRU) {
1615
+ return this.selectFromHotQueueFrequencyWeighted(items);
1616
+ } else {
1617
+ return this.selectFromHotQueueLRU(items);
1618
+ }
1619
+ }
1620
+ /**
1621
+ * Select eviction candidate from hot queue using traditional LRU
1622
+ */
1623
+ selectFromHotQueueLRU(items) {
1624
+ let oldestKey = null;
1625
+ let oldestTime = Infinity;
1626
+ for (const key of this.hotQueue) {
1627
+ const metadata = items.get(key);
1628
+ if (metadata && metadata.lastAccessedAt < oldestTime) {
1629
+ oldestTime = metadata.lastAccessedAt;
1630
+ oldestKey = key;
1631
+ }
1632
+ }
1633
+ return oldestKey || (items.size > 0 ? items.keys().next().value ?? null : null);
1634
+ }
1635
+ /**
1636
+ * Select eviction candidate from hot queue using frequency-weighted LRU
1637
+ */
1638
+ selectFromHotQueueFrequencyWeighted(items) {
1639
+ let bestKey = null;
1640
+ let lowestScore = Infinity;
1641
+ for (const key of this.hotQueue) {
1642
+ const metadata = items.get(key);
1643
+ if (!metadata) continue;
1644
+ const frequency = this.getEffectiveFrequency(metadata);
1645
+ const timeFactor = Date.now() - metadata.lastAccessedAt;
1646
+ const normalizedTimeFactor = timeFactor / (1e3 * 60);
1647
+ const score = normalizedTimeFactor / Math.max(1, frequency);
1648
+ if (score < lowestScore) {
1649
+ lowestScore = score;
1650
+ bestKey = key;
1651
+ }
1652
+ }
1653
+ return bestKey || (items.size > 0 ? items.keys().next().value ?? null : null);
1654
+ }
1655
+ onItemAccessed(key, metadata) {
1656
+ const now = Date.now();
1657
+ metadata.lastAccessedAt = now;
1658
+ metadata.accessCount++;
1659
+ metadata.rawFrequency = metadata.accessCount;
1660
+ if ((this.config.hotQueueDecayFactor ?? 0) > 0) {
1661
+ metadata.frequencyScore = this.calculateFrequencyScore(metadata, now);
1662
+ metadata.lastFrequencyUpdate = now;
1663
+ }
1664
+ const recentIndex = this.recentQueue.indexOf(key);
1665
+ if (recentIndex !== -1) {
1666
+ if (this.shouldPromoteToHotQueue(metadata)) {
1667
+ this.recentQueue.splice(recentIndex, 1);
1668
+ this.hotQueue.unshift(key);
1669
+ }
1670
+ } else {
1671
+ const hotIndex = this.hotQueue.indexOf(key);
1672
+ if (hotIndex !== -1) {
1673
+ this.hotQueue.splice(hotIndex, 1);
1674
+ this.hotQueue.unshift(key);
1675
+ }
1676
+ }
1677
+ }
1678
+ onItemAdded(key, metadata) {
1679
+ const now = Date.now();
1680
+ metadata.addedAt = now;
1681
+ metadata.lastAccessedAt = now;
1682
+ metadata.accessCount = 1;
1683
+ metadata.rawFrequency = 1;
1684
+ if ((this.config.hotQueueDecayFactor ?? 0) > 0) {
1685
+ metadata.frequencyScore = 1;
1686
+ metadata.lastFrequencyUpdate = now;
1687
+ }
1688
+ if (this.ghostQueue.has(key)) {
1689
+ this.ghostQueue.delete(key);
1690
+ this.hotQueue.unshift(key);
1691
+ } else {
1692
+ this.recentQueue.unshift(key);
1693
+ if (this.recentQueue.length > this.maxRecentSize) {
1694
+ const evicted = this.recentQueue.pop();
1695
+ if (evicted) {
1696
+ this.ghostQueue.add(evicted);
1697
+ }
1698
+ }
1699
+ }
1700
+ if (this.ghostQueue.size > this.maxGhostSize) {
1701
+ const firstKey = this.ghostQueue.values().next().value;
1702
+ if (firstKey) {
1703
+ this.ghostQueue.delete(firstKey);
1704
+ }
1705
+ }
1706
+ }
1707
+ onItemRemoved(key) {
1708
+ const recentIndex = this.recentQueue.indexOf(key);
1709
+ if (recentIndex !== -1) {
1710
+ this.recentQueue.splice(recentIndex, 1);
1711
+ }
1712
+ const hotIndex = this.hotQueue.indexOf(key);
1713
+ if (hotIndex !== -1) {
1714
+ this.hotQueue.splice(hotIndex, 1);
1715
+ }
1716
+ }
1717
+ /**
1718
+ * Determine if an item should be promoted from recent to hot queue
1719
+ */
1720
+ shouldPromoteToHotQueue(metadata) {
1721
+ if (!this.config.useFrequencyPromotion) {
1722
+ return metadata.accessCount >= 2;
1723
+ }
1724
+ const threshold = this.config.promotionThreshold ?? 2;
1725
+ const frequency = this.getEffectiveFrequency(metadata);
1726
+ return frequency >= threshold;
1727
+ }
1728
+ /**
1729
+ * Get effective frequency for an item, applying decay if enabled
1730
+ */
1731
+ getEffectiveFrequency(metadata) {
1732
+ if ((this.config.hotQueueDecayFactor ?? 0) === 0) {
1733
+ return metadata.rawFrequency || metadata.accessCount;
1734
+ }
1735
+ const now = Date.now();
1736
+ if (typeof metadata.frequencyScore === "number" && typeof metadata.lastFrequencyUpdate === "number") {
1737
+ const timeSinceUpdate = now - metadata.lastFrequencyUpdate;
1738
+ const decayAmount = timeSinceUpdate / (this.config.hotQueueDecayInterval ?? 3e5) * (this.config.hotQueueDecayFactor ?? 0.05);
1739
+ return Math.max(1, metadata.frequencyScore * (1 - decayAmount));
1740
+ }
1741
+ return metadata.rawFrequency || metadata.accessCount;
1742
+ }
1743
+ /**
1744
+ * Calculate frequency score with decay applied
1745
+ */
1746
+ calculateFrequencyScore(metadata, currentTime) {
1747
+ const rawFreq = metadata.rawFrequency || metadata.accessCount;
1748
+ if (typeof metadata.lastFrequencyUpdate !== "number") {
1749
+ return rawFreq;
1750
+ }
1751
+ const timeSinceUpdate = currentTime - metadata.lastFrequencyUpdate;
1752
+ const decayAmount = timeSinceUpdate / (this.config.hotQueueDecayInterval ?? 3e5) * (this.config.hotQueueDecayFactor ?? 0.05);
1753
+ const previousScore = metadata.frequencyScore || rawFreq;
1754
+ const decayedScore = previousScore * (1 - decayAmount);
1755
+ return Math.max(1, decayedScore + 1);
1756
+ }
1757
+ /**
1758
+ * Apply periodic decay to hot queue items
1759
+ */
1760
+ applyPeriodicDecay(items) {
1761
+ if ((this.config.hotQueueDecayFactor ?? 0) === 0) return;
1762
+ const now = Date.now();
1763
+ const timeSinceDecay = now - this.lastDecayTime;
1764
+ if (timeSinceDecay >= (this.config.hotQueueDecayInterval ?? 3e5)) {
1765
+ if (this.hotQueue.length > 0) {
1766
+ for (const key of this.hotQueue) {
1767
+ const metadata = items.get(key);
1768
+ if (metadata && typeof metadata.frequencyScore === "number") {
1769
+ const decayAmount = this.config.hotQueueDecayFactor ?? 0.05;
1770
+ metadata.frequencyScore = Math.max(1, metadata.frequencyScore * (1 - decayAmount));
1771
+ }
1772
+ }
1773
+ this.lastDecayTime = now;
1774
+ }
1775
+ }
1776
+ }
1777
+ /**
1778
+ * Get configuration for this strategy
1779
+ */
1780
+ getConfig() {
1781
+ return { ...this.config };
1782
+ }
1783
+ /**
1784
+ * Reset internal state (useful for testing)
1785
+ */
1786
+ reset() {
1787
+ this.recentQueue = [];
1788
+ this.hotQueue = [];
1789
+ this.ghostQueue.clear();
1790
+ this.lastDecayTime = Date.now();
1791
+ }
1792
+ };
1793
+
1794
+ // src/eviction/EvictionStrategyFactory.ts
1795
+ function createEvictionStrategy(policy, maxCacheSize, config) {
1796
+ const safeMaxCacheSize = typeof maxCacheSize === "number" && maxCacheSize > 0 ? maxCacheSize : 1e3;
1797
+ switch (policy) {
1798
+ case "lru":
1799
+ return new LRUEvictionStrategy();
1800
+ case "lfu": {
1801
+ try {
1802
+ const lfuConfig = config?.type === "lfu" ? config : { type: "lfu" };
1803
+ return new LFUEvictionStrategy(lfuConfig);
1804
+ } catch (error) {
1805
+ const errorMessage = error instanceof Error ? error.message : String(error);
1806
+ console.warn(`Failed to create lfu strategy with provided configuration, falling back to LRU:`, errorMessage);
1807
+ return new LRUEvictionStrategy();
1808
+ }
1809
+ }
1810
+ case "fifo":
1811
+ return new FIFOEvictionStrategy();
1812
+ case "mru":
1813
+ return new MRUEvictionStrategy();
1814
+ case "random":
1815
+ return new RandomEvictionStrategy();
1816
+ case "arc": {
1817
+ try {
1818
+ const arcConfig = config?.type === "arc" ? config : { ...DEFAULT_ARC_CONFIG, maxCacheSize: safeMaxCacheSize };
1819
+ const finalMaxSize = arcConfig.maxCacheSize && arcConfig.maxCacheSize > 0 ? arcConfig.maxCacheSize : safeMaxCacheSize;
1820
+ return new ARCEvictionStrategy(finalMaxSize, { ...arcConfig, maxCacheSize: finalMaxSize });
1821
+ } catch (error) {
1822
+ const errorMessage = error instanceof Error ? error.message : String(error);
1823
+ console.warn(`Failed to create arc strategy with provided configuration, falling back to LRU:`, errorMessage);
1824
+ return new LRUEvictionStrategy();
1825
+ }
1826
+ }
1827
+ case "2q": {
1828
+ try {
1829
+ const twoQConfig = config?.type === "2q" ? config : { ...DEFAULT_TWO_QUEUE_CONFIG, maxCacheSize: safeMaxCacheSize };
1830
+ const finalMaxSize = twoQConfig.maxCacheSize && twoQConfig.maxCacheSize > 0 ? twoQConfig.maxCacheSize : safeMaxCacheSize;
1831
+ return new TwoQueueEvictionStrategy(finalMaxSize, { ...twoQConfig, maxCacheSize: finalMaxSize });
1832
+ } catch (error) {
1833
+ const errorMessage = error instanceof Error ? error.message : String(error);
1834
+ console.warn(`Failed to create 2q strategy with provided configuration, falling back to LRU:`, errorMessage);
1835
+ return new LRUEvictionStrategy();
1836
+ }
1837
+ }
1838
+ default:
1839
+ throw new Error(`Unsupported eviction policy: ${policy}`);
1840
+ }
1841
+ }
1842
+
1843
+ // src/utils/CacheSize.ts
1844
+ var SIZE_UNITS = {
1845
+ // Decimal units (powers of 1000)
1846
+ "b": 1,
1847
+ "byte": 1,
1848
+ "bytes": 1,
1849
+ "kb": 1e3,
1850
+ "kilobyte": 1e3,
1851
+ "kilobytes": 1e3,
1852
+ "mb": 1e3 * 1e3,
1853
+ "megabyte": 1e3 * 1e3,
1854
+ "megabytes": 1e3 * 1e3,
1855
+ "gb": 1e3 * 1e3 * 1e3,
1856
+ "gigabyte": 1e3 * 1e3 * 1e3,
1857
+ "gigabytes": 1e3 * 1e3 * 1e3,
1858
+ "tb": 1e3 * 1e3 * 1e3 * 1e3,
1859
+ "terabyte": 1e3 * 1e3 * 1e3 * 1e3,
1860
+ "terabytes": 1e3 * 1e3 * 1e3 * 1e3,
1861
+ // Binary units (powers of 1024)
1862
+ "kib": 1024,
1863
+ "kibibyte": 1024,
1864
+ "kibibytes": 1024,
1865
+ "mib": 1024 * 1024,
1866
+ "mebibyte": 1024 * 1024,
1867
+ "mebibytes": 1024 * 1024,
1868
+ "gib": 1024 * 1024 * 1024,
1869
+ "gibibyte": 1024 * 1024 * 1024,
1870
+ "gibibytes": 1024 * 1024 * 1024,
1871
+ "tib": 1024 * 1024 * 1024 * 1024,
1872
+ "tebibyte": 1024 * 1024 * 1024 * 1024,
1873
+ "tebibytes": 1024 * 1024 * 1024 * 1024
1874
+ };
1875
+ function parseSizeString(sizeStr) {
1876
+ if (!sizeStr || typeof sizeStr !== "string") {
1877
+ throw new Error("Size string must be a non-empty string");
1878
+ }
1879
+ const trimmed = sizeStr.trim();
1880
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
1881
+ const bytes = parseFloat(trimmed);
1882
+ if (isNaN(bytes) || bytes < 0) {
1883
+ throw new Error(`Invalid size value: ${sizeStr}`);
1884
+ }
1885
+ return Math.floor(bytes);
1886
+ }
1887
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*([a-zA-Z]+)$/);
1888
+ if (!match) {
1889
+ throw new Error(`Invalid size format: ${sizeStr}. Expected format: '100', '5KB', '10MB', etc.`);
1890
+ }
1891
+ const [, valueStr, unitStr] = match;
1892
+ const value = parseFloat(valueStr);
1893
+ const unit = unitStr.toLowerCase();
1894
+ if (isNaN(value) || value < 0) {
1895
+ throw new Error(`Invalid size value: ${valueStr}`);
1896
+ }
1897
+ const multiplier = SIZE_UNITS[unit];
1898
+ if (!(unit in SIZE_UNITS)) {
1899
+ const supportedUnits = Object.keys(SIZE_UNITS).filter((u) => u.length <= 3).join(", ");
1900
+ throw new Error(`Unsupported size unit: ${unitStr}. Supported units: ${supportedUnits}`);
1901
+ }
1902
+ return Math.floor(value * multiplier);
1903
+ }
1904
+ function formatBytes(bytes, binary = false) {
1905
+ if (bytes === 0) return "0 B";
1906
+ if (bytes < 0) return `${bytes} B`;
1907
+ const k = binary ? 1024 : 1e3;
1908
+ const sizes = binary ? ["B", "KiB", "MiB", "GiB", "TiB", "PiB"] : ["B", "KB", "MB", "GB", "TB", "PB"];
1909
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1910
+ const size = bytes / Math.pow(k, i);
1911
+ const formatted = size % 1 === 0 ? size.toString() : size.toFixed(1);
1912
+ return `${formatted} ${sizes[i]}`;
1913
+ }
1914
+ function estimateValueSize(value) {
1915
+ if (value === null || typeof value === "undefined") {
1916
+ return 8;
1917
+ }
1918
+ switch (typeof value) {
1919
+ case "boolean":
1920
+ return 4;
1921
+ case "number":
1922
+ return 8;
1923
+ case "string":
1924
+ return value.length * 2;
1925
+ case "object":
1926
+ if (Array.isArray(value)) {
1927
+ return value.reduce((total, item) => total + estimateValueSize(item), 24);
1928
+ }
1929
+ try {
1930
+ const jsonString = JSON.stringify(value);
1931
+ return jsonString.length * 2 + 16;
1932
+ } catch {
1933
+ return 64;
1934
+ }
1935
+ default:
1936
+ return 32;
1937
+ }
1938
+ }
1939
+ function validateSizeConfig(config) {
1940
+ if (typeof config.maxSizeBytes !== "undefined") {
1941
+ try {
1942
+ const bytes = parseSizeString(config.maxSizeBytes);
1943
+ if (bytes <= 0) {
1944
+ throw new Error("maxSizeBytes must be positive");
1945
+ }
1946
+ } catch (error) {
1947
+ throw new Error(`Invalid maxSizeBytes: ${error instanceof Error ? error.message : "Unknown error"}`);
1948
+ }
1949
+ }
1950
+ if (typeof config.maxItems !== "undefined") {
1951
+ if (!Number.isInteger(config.maxItems) || config.maxItems <= 0) {
1952
+ throw new Error("maxItems must be a positive integer");
1953
+ }
1954
+ }
1955
+ }
1956
+
1957
+ // src/memory/EnhancedMemoryCacheMap.ts
1958
+ var logger16 = logger_default.get("EnhancedMemoryCacheMap");
1959
+ var EnhancedMemoryCacheMap = class _EnhancedMemoryCacheMap extends CacheMap {
1960
+ map = {};
1961
+ normalizedHashFunction;
1962
+ // Query result cache: maps query hash to cache entry with expiration
1963
+ queryResultCache = {};
1964
+ // Mutex-like tracking for TTL operations to prevent race conditions
1965
+ ttlOperationsInProgress = /* @__PURE__ */ new Set();
1966
+ // Size tracking
1967
+ currentSizeBytes = 0;
1968
+ currentItemCount = 0;
1969
+ queryResultsCacheSize = 0;
1970
+ // Size limits
1971
+ maxSizeBytes;
1972
+ maxItems;
1973
+ // Eviction strategy
1974
+ evictionStrategy;
1975
+ constructor(types, sizeConfig, initialData) {
1976
+ super(types);
1977
+ this.normalizedHashFunction = createNormalizedHashFunction();
1978
+ if (sizeConfig?.maxSizeBytes) {
1979
+ this.maxSizeBytes = parseSizeString(sizeConfig.maxSizeBytes);
1980
+ logger16.debug("Cache size limit set", { maxSizeBytes: this.maxSizeBytes });
1981
+ }
1982
+ if (sizeConfig?.maxItems) {
1983
+ this.maxItems = sizeConfig.maxItems;
1984
+ logger16.debug("Cache item limit set", { maxItems: this.maxItems });
1985
+ }
1986
+ const policy = sizeConfig?.evictionPolicy || "lru";
1987
+ const maxCacheSize = this.maxItems || 1e3;
1988
+ this.evictionStrategy = createEvictionStrategy(policy, maxCacheSize);
1989
+ logger16.debug("Eviction strategy initialized", { policy });
1990
+ if (initialData) {
1991
+ for (const [keyStr, value] of Object.entries(initialData)) {
1992
+ try {
1993
+ const key = JSON.parse(keyStr);
1994
+ this.set(key, value);
1995
+ } catch (error) {
1996
+ logger16.error("Failed to parse initial data key", { keyStr, error });
1997
+ }
1998
+ }
1999
+ }
2000
+ }
2001
+ get(key) {
2002
+ logger16.trace("get", { key });
2003
+ const hashedKey = this.normalizedHashFunction(key);
2004
+ const entry = this.map[hashedKey];
2005
+ if (entry && this.normalizedHashFunction(entry.originalKey) === hashedKey) {
2006
+ this.evictionStrategy.onItemAccessed(hashedKey, entry.metadata);
2007
+ return entry.value;
2008
+ }
2009
+ return null;
2010
+ }
2011
+ getWithTTL(key, ttl) {
2012
+ logger16.trace("getWithTTL", { key, ttl });
2013
+ if (ttl === 0) {
2014
+ return null;
2015
+ }
2016
+ const hashedKey = this.normalizedHashFunction(key);
2017
+ const entry = this.map[hashedKey];
2018
+ if (entry && this.normalizedHashFunction(entry.originalKey) === hashedKey) {
2019
+ const now = Date.now();
2020
+ const age = now - entry.metadata.addedAt;
2021
+ if (age >= ttl) {
2022
+ logger16.trace("Item expired, removing from enhanced cache", { key, age, ttl });
2023
+ this.delete(key);
2024
+ return null;
2025
+ }
2026
+ this.evictionStrategy.onItemAccessed(hashedKey, entry.metadata);
2027
+ return entry.value;
2028
+ }
2029
+ return null;
2030
+ }
2031
+ set(key, value) {
2032
+ logger16.trace("set", { key, value });
2033
+ const hashedKey = this.normalizedHashFunction(key);
2034
+ const estimatedSize = estimateValueSize(value);
2035
+ const existingEntry = this.map[hashedKey];
2036
+ const isUpdate = existingEntry && this.normalizedHashFunction(existingEntry.originalKey) === hashedKey;
2037
+ if (isUpdate) {
2038
+ const sizeDiff = estimatedSize - existingEntry.metadata.estimatedSize;
2039
+ this.currentSizeBytes += sizeDiff;
2040
+ const oldValue = existingEntry.value;
2041
+ existingEntry.value = value;
2042
+ existingEntry.metadata.estimatedSize = estimatedSize;
2043
+ this.evictionStrategy.onItemRemoved(hashedKey);
2044
+ this.evictionStrategy.onItemAdded(hashedKey, existingEntry.metadata);
2045
+ logger16.trace("Updated existing cache entry", {
2046
+ key: hashedKey,
2047
+ sizeDiff,
2048
+ currentSize: this.currentSizeBytes,
2049
+ oldValue: oldValue !== value
2050
+ });
2051
+ } else {
2052
+ this.ensureSpaceAvailable(estimatedSize);
2053
+ const metadata = {
2054
+ addedAt: Date.now(),
2055
+ lastAccessedAt: Date.now(),
2056
+ accessCount: 0,
2057
+ estimatedSize,
2058
+ key: hashedKey
2059
+ };
2060
+ this.map[hashedKey] = {
2061
+ originalKey: key,
2062
+ value,
2063
+ metadata
2064
+ };
2065
+ this.currentSizeBytes += estimatedSize;
2066
+ this.currentItemCount++;
2067
+ this.evictionStrategy.onItemAdded(hashedKey, metadata);
2068
+ logger16.trace("Added new cache entry", {
2069
+ key: hashedKey,
2070
+ size: estimatedSize,
2071
+ currentSize: this.currentSizeBytes,
2072
+ currentCount: this.currentItemCount
2073
+ });
2074
+ }
2075
+ }
2076
+ includesKey(key) {
2077
+ const hashedKey = this.normalizedHashFunction(key);
2078
+ const entry = this.map[hashedKey];
2079
+ return !!entry && this.normalizedHashFunction(entry.originalKey) === hashedKey;
2080
+ }
2081
+ delete(key) {
2082
+ logger16.trace("delete", { key });
2083
+ const hashedKey = this.normalizedHashFunction(key);
2084
+ const entry = this.map[hashedKey];
2085
+ if (entry && this.normalizedHashFunction(entry.originalKey) === hashedKey) {
2086
+ this.currentSizeBytes -= entry.metadata.estimatedSize;
2087
+ this.currentItemCount--;
2088
+ this.evictionStrategy.onItemRemoved(hashedKey);
2089
+ delete this.map[hashedKey];
2090
+ logger16.trace("Deleted cache entry", {
2091
+ key: hashedKey,
2092
+ freedSize: entry.metadata.estimatedSize,
2093
+ currentSize: this.currentSizeBytes,
2094
+ currentCount: this.currentItemCount
2095
+ });
2096
+ }
2097
+ }
2098
+ keys() {
2099
+ return Object.values(this.map).map((entry) => entry.originalKey);
2100
+ }
2101
+ values() {
2102
+ return Object.values(this.map).map((entry) => entry.value);
2103
+ }
2104
+ clear() {
2105
+ logger16.debug("Clearing cache", {
2106
+ itemsCleared: this.currentItemCount,
2107
+ bytesFreed: this.currentSizeBytes
2108
+ });
2109
+ for (const hashedKey of Object.keys(this.map)) {
2110
+ this.evictionStrategy.onItemRemoved(hashedKey);
2111
+ }
2112
+ this.map = {};
2113
+ this.currentSizeBytes = 0;
2114
+ this.currentItemCount = 0;
2115
+ }
2116
+ allIn(locations) {
2117
+ const allValues = this.values();
2118
+ if (locations.length === 0) {
2119
+ logger16.debug("Returning all items, LocKeys is empty");
2120
+ return allValues;
2121
+ } else {
2122
+ logger16.debug("allIn", { locations, count: allValues.length });
2123
+ return allValues.filter((item) => {
2124
+ const key = item.key;
2125
+ if (key && isComKey2(key)) {
2126
+ return isLocKeyArrayEqual(locations, key.loc);
2127
+ }
2128
+ return false;
2129
+ });
2130
+ }
2131
+ }
2132
+ contains(query, locations) {
2133
+ logger16.debug("contains", { query, locations });
2134
+ const items = this.allIn(locations);
2135
+ return items.some((item) => isQueryMatch2(item, query));
2136
+ }
2137
+ queryIn(query, locations = []) {
2138
+ logger16.debug("queryIn", { query, locations });
2139
+ const items = this.allIn(locations);
2140
+ return items.filter((item) => isQueryMatch2(item, query));
2141
+ }
2142
+ clone() {
2143
+ const sizeConfig = {};
2144
+ if (this.maxSizeBytes) {
2145
+ sizeConfig.maxSizeBytes = this.maxSizeBytes.toString();
2146
+ }
2147
+ if (this.maxItems) {
2148
+ sizeConfig.maxItems = this.maxItems;
2149
+ }
2150
+ const clone = new _EnhancedMemoryCacheMap(this.types, sizeConfig);
2151
+ for (const key of this.keys()) {
2152
+ const value = this.get(key);
2153
+ if (value) {
2154
+ clone.set(key, value);
2155
+ }
2156
+ }
2157
+ return clone;
2158
+ }
2159
+ /**
2160
+ * Get current cache statistics
2161
+ */
2162
+ getStats() {
2163
+ const stats = {
2164
+ currentSizeBytes: this.currentSizeBytes,
2165
+ currentItemCount: this.currentItemCount,
2166
+ maxSizeBytes: this.maxSizeBytes,
2167
+ maxItems: this.maxItems,
2168
+ utilizationPercent: {}
2169
+ };
2170
+ if (this.maxSizeBytes) {
2171
+ stats.utilizationPercent.bytes = this.currentSizeBytes / this.maxSizeBytes * 100;
2172
+ }
2173
+ if (this.maxItems) {
2174
+ stats.utilizationPercent.items = this.currentItemCount / this.maxItems * 100;
2175
+ }
2176
+ return stats;
2177
+ }
2178
+ /**
2179
+ * Ensure there's space available for a new item of the given size
2180
+ * Evicts items if necessary based on the configured eviction policy
2181
+ */
2182
+ ensureSpaceAvailable(newItemSize) {
2183
+ const itemMetadata = /* @__PURE__ */ new Map();
2184
+ for (const [hashedKey, entry] of Object.entries(this.map)) {
2185
+ itemMetadata.set(hashedKey, entry.metadata);
2186
+ }
2187
+ while (this.maxItems && this.currentItemCount >= this.maxItems) {
2188
+ const keyToEvict = this.evictionStrategy.selectForEviction(itemMetadata);
2189
+ if (!keyToEvict) {
2190
+ logger16.debug("No item selected for eviction despite being over item limit");
2191
+ break;
2192
+ }
2193
+ this.evictItem(keyToEvict, itemMetadata);
2194
+ logger16.debug("Evicted item due to count limit", {
2195
+ evictedKey: keyToEvict,
2196
+ currentCount: this.currentItemCount,
2197
+ maxItems: this.maxItems
2198
+ });
2199
+ }
2200
+ while (this.maxSizeBytes && this.getTotalSizeBytes() + newItemSize > this.maxSizeBytes) {
2201
+ const keyToEvict = this.evictionStrategy.selectForEviction(itemMetadata);
2202
+ if (!keyToEvict) {
2203
+ logger16.debug("No item selected for eviction despite being over size limit");
2204
+ break;
2205
+ }
2206
+ this.evictItem(keyToEvict, itemMetadata);
2207
+ logger16.debug("Evicted item due to size limit", {
2208
+ evictedKey: keyToEvict,
2209
+ currentItemsSize: this.currentSizeBytes,
2210
+ queryResultsSize: this.queryResultsCacheSize,
2211
+ totalSize: this.getTotalSizeBytes(),
2212
+ newItemSize,
2213
+ maxSizeBytes: this.maxSizeBytes
2214
+ });
2215
+ }
2216
+ }
2217
+ /**
2218
+ * Evict a specific item and update metadata tracking
2219
+ */
2220
+ evictItem(hashedKey, itemMetadata) {
2221
+ const entry = this.map[hashedKey];
2222
+ if (entry) {
2223
+ const originalKey = entry.originalKey;
2224
+ this.delete(originalKey);
2225
+ itemMetadata.delete(hashedKey);
2226
+ }
2227
+ }
2228
+ // Query result caching methods
2229
+ setQueryResult(queryHash, itemKeys, ttl) {
2230
+ logger16.trace("setQueryResult", { queryHash, itemKeys, ttl });
2231
+ if (queryHash in this.queryResultCache) {
2232
+ this.removeQueryResultFromSizeTracking(queryHash);
2233
+ }
2234
+ const entry = {
2235
+ itemKeys: [...itemKeys]
2236
+ // Create a copy to avoid external mutations
2237
+ };
2238
+ if (ttl) {
2239
+ entry.expiresAt = Date.now() + ttl;
2240
+ }
2241
+ this.queryResultCache[queryHash] = entry;
2242
+ this.addQueryResultToSizeTracking(queryHash, entry);
2243
+ }
2244
+ getQueryResult(queryHash) {
2245
+ logger16.trace("getQueryResult", { queryHash });
2246
+ if (this.ttlOperationsInProgress.has(queryHash)) {
2247
+ const entry2 = this.queryResultCache[queryHash];
2248
+ return entry2 ? [...entry2.itemKeys] : null;
2249
+ }
2250
+ const entry = this.queryResultCache[queryHash];
2251
+ if (!entry) {
2252
+ return null;
2253
+ }
2254
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
2255
+ this.ttlOperationsInProgress.add(queryHash);
2256
+ try {
2257
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
2258
+ logger16.trace("Query result expired, removing", { queryHash, expiresAt: entry.expiresAt });
2259
+ this.removeQueryResultFromSizeTracking(queryHash);
2260
+ delete this.queryResultCache[queryHash];
2261
+ return null;
2262
+ }
2263
+ } finally {
2264
+ this.ttlOperationsInProgress.delete(queryHash);
2265
+ }
2266
+ }
2267
+ return [...entry.itemKeys];
2268
+ }
2269
+ hasQueryResult(queryHash) {
2270
+ if (this.ttlOperationsInProgress.has(queryHash)) {
2271
+ return queryHash in this.queryResultCache;
2272
+ }
2273
+ const entry = this.queryResultCache[queryHash];
2274
+ if (!entry) {
2275
+ return false;
2276
+ }
2277
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
2278
+ this.ttlOperationsInProgress.add(queryHash);
2279
+ try {
2280
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
2281
+ this.removeQueryResultFromSizeTracking(queryHash);
2282
+ delete this.queryResultCache[queryHash];
2283
+ return false;
2284
+ }
2285
+ } finally {
2286
+ this.ttlOperationsInProgress.delete(queryHash);
2287
+ }
2288
+ }
2289
+ return true;
2290
+ }
2291
+ deleteQueryResult(queryHash) {
2292
+ if (queryHash in this.queryResultCache) {
2293
+ this.removeQueryResultFromSizeTracking(queryHash);
2294
+ delete this.queryResultCache[queryHash];
2295
+ }
2296
+ }
2297
+ clearQueryResults() {
2298
+ this.queryResultCache = {};
2299
+ this.queryResultsCacheSize = 0;
2300
+ }
2301
+ invalidateItemKeys(keys) {
2302
+ logger16.debug("invalidateItemKeys", { keys });
2303
+ keys.forEach((key) => {
2304
+ this.delete(key);
2305
+ });
2306
+ }
2307
+ invalidateLocation(locations) {
2308
+ logger16.debug("invalidateLocation", { locations });
2309
+ if (locations.length === 0) {
2310
+ const allKeys = this.keys();
2311
+ const primaryKeys = allKeys.filter((key) => !isComKey2(key));
2312
+ this.invalidateItemKeys(primaryKeys);
2313
+ } else {
2314
+ const itemsInLocation = this.allIn(locations);
2315
+ const keysToInvalidate = itemsInLocation.map((item) => item.key);
2316
+ this.invalidateItemKeys(keysToInvalidate);
2317
+ }
2318
+ this.clearQueryResults();
2319
+ }
2320
+ /**
2321
+ * Add query result to size tracking
2322
+ */
2323
+ addQueryResultToSizeTracking(queryHash, entry) {
2324
+ const hashSize = estimateValueSize(queryHash);
2325
+ const itemKeysSize = estimateValueSize(entry.itemKeys);
2326
+ const metadataSize = entry.expiresAt ? 8 : 0;
2327
+ const totalSize = hashSize + itemKeysSize + metadataSize;
2328
+ this.queryResultsCacheSize += totalSize;
2329
+ logger16.trace("Added query result to size tracking", {
2330
+ queryHash,
2331
+ estimatedSize: totalSize,
2332
+ totalQueryCacheSize: this.queryResultsCacheSize
2333
+ });
2334
+ }
2335
+ /**
2336
+ * Remove query result from size tracking
2337
+ */
2338
+ removeQueryResultFromSizeTracking(queryHash) {
2339
+ const entry = this.queryResultCache[queryHash];
2340
+ if (entry) {
2341
+ const hashSize = estimateValueSize(queryHash);
2342
+ const itemKeysSize = estimateValueSize(entry.itemKeys);
2343
+ const metadataSize = entry.expiresAt ? 8 : 0;
2344
+ const totalSize = hashSize + itemKeysSize + metadataSize;
2345
+ this.queryResultsCacheSize = Math.max(0, this.queryResultsCacheSize - totalSize);
2346
+ logger16.trace("Removed query result from size tracking", {
2347
+ queryHash,
2348
+ estimatedSize: totalSize,
2349
+ totalQueryCacheSize: this.queryResultsCacheSize
2350
+ });
2351
+ }
2352
+ }
2353
+ /**
2354
+ * Get total cache size including query results
2355
+ */
2356
+ getTotalSizeBytes() {
2357
+ return this.currentSizeBytes + this.queryResultsCacheSize;
2358
+ }
2359
+ };
2360
+
2361
+ // src/browser/LocalStorageCacheMap.ts
2362
+ import {
2363
+ isComKey as isComKey3,
2364
+ isQueryMatch as isQueryMatch3
2365
+ } from "@fjell/core";
2366
+ var logger17 = logger_default.get("LocalStorageCacheMap");
2367
+ var LocalStorageCacheMap = class _LocalStorageCacheMap extends CacheMap {
2368
+ keyPrefix;
2369
+ normalizedHashFunction;
2370
+ fallbackCache = null;
2371
+ quotaExceeded = false;
2372
+ constructor(types, keyPrefix = "fjell-cache") {
2373
+ super(types);
2374
+ this.keyPrefix = keyPrefix;
2375
+ this.normalizedHashFunction = createNormalizedHashFunction();
2376
+ }
2377
+ getStorageKey(key) {
2378
+ const hashedKey = this.normalizedHashFunction(key);
2379
+ return `${this.keyPrefix}:${hashedKey}`;
2380
+ }
2381
+ initializeFallbackCache() {
2382
+ if (!this.fallbackCache) {
2383
+ this.fallbackCache = new MemoryCacheMap(this.types);
2384
+ logger17.warning("LocalStorage quota exceeded, falling back to in-memory cache");
2385
+ }
2386
+ }
2387
+ isQuotaExceededError(error) {
2388
+ return error && (error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED" || error.code === 22 || error.code === 1014);
2389
+ }
2390
+ tryCleanupOldEntries() {
2391
+ try {
2392
+ const allEntries = this.collectCacheEntries();
2393
+ return this.removeOldestEntries(allEntries);
2394
+ } catch (error) {
2395
+ logger17.error("Failed to cleanup old localStorage entries", { error });
2396
+ return false;
2397
+ }
2398
+ }
2399
+ collectCacheEntries() {
2400
+ const allEntries = [];
2401
+ for (let i = 0; i < localStorage.length; i++) {
2402
+ const key = localStorage.key(i);
2403
+ if (!key || !key.startsWith(this.keyPrefix + ":")) {
2404
+ continue;
2405
+ }
2406
+ try {
2407
+ const stored = localStorage.getItem(key);
2408
+ if (stored) {
2409
+ const parsed = JSON.parse(stored);
2410
+ allEntries.push({ key, timestamp: parsed.timestamp || 0 });
2411
+ }
2412
+ } catch {
2413
+ allEntries.push({ key, timestamp: 0 });
2414
+ }
2415
+ }
2416
+ return allEntries;
2417
+ }
2418
+ removeOldestEntries(allEntries) {
2419
+ allEntries.sort((a, b) => a.timestamp - b.timestamp);
2420
+ const toRemove = Math.ceil(allEntries.length * 0.25);
2421
+ for (let i = 0; i < toRemove; i++) {
2422
+ localStorage.removeItem(allEntries[i].key);
2423
+ }
2424
+ logger17.info(`Cleaned up ${toRemove} old localStorage entries to free space`);
2425
+ return toRemove > 0;
2426
+ }
2427
+ getAllStorageKeys() {
2428
+ const keys = [];
2429
+ for (let i = 0; i < localStorage.length; i++) {
2430
+ const key = localStorage.key(i);
2431
+ if (key && key.startsWith(`${this.keyPrefix}:`)) {
2432
+ keys.push(key);
2433
+ }
2434
+ }
2435
+ return keys;
2436
+ }
2437
+ get(key) {
2438
+ logger17.trace("get", { key });
2439
+ if (this.quotaExceeded && this.fallbackCache) {
2440
+ const memoryResult = this.fallbackCache.get(key);
2441
+ if (memoryResult) {
2442
+ return memoryResult;
2443
+ }
2444
+ }
2445
+ try {
2446
+ const storageKey = this.getStorageKey(key);
2447
+ const stored = localStorage.getItem(storageKey);
2448
+ if (stored) {
2449
+ const parsed = JSON.parse(stored);
2450
+ if (this.normalizedHashFunction(parsed.originalKey) === this.normalizedHashFunction(key)) {
2451
+ return parsed.value;
2452
+ }
2453
+ }
2454
+ if (!this.quotaExceeded && this.fallbackCache) {
2455
+ return this.fallbackCache.get(key);
2456
+ }
2457
+ return null;
2458
+ } catch (error) {
2459
+ logger17.error("Error retrieving from localStorage", { key, error });
2460
+ if (this.fallbackCache) {
2461
+ return this.fallbackCache.get(key);
2462
+ }
2463
+ return null;
2464
+ }
2465
+ }
2466
+ getWithTTL(key, ttl) {
2467
+ logger17.trace("getWithTTL", { key, ttl });
2468
+ if (ttl === 0) {
2469
+ return null;
2470
+ }
2471
+ try {
2472
+ const storageKey = this.getStorageKey(key);
2473
+ const stored = localStorage.getItem(storageKey);
2474
+ if (stored) {
2475
+ const parsed = JSON.parse(stored);
2476
+ if (this.normalizedHashFunction(parsed.originalKey) !== this.normalizedHashFunction(key)) {
2477
+ return null;
2478
+ }
2479
+ if (typeof parsed.timestamp === "number" && !isNaN(parsed.timestamp)) {
2480
+ const now = Date.now();
2481
+ const age = now - parsed.timestamp;
2482
+ if (age >= ttl) {
2483
+ logger17.trace("Item expired, removing from localStorage", { key, age, ttl });
2484
+ localStorage.removeItem(storageKey);
2485
+ return null;
2486
+ }
2487
+ }
2488
+ return parsed.value;
2489
+ }
2490
+ return null;
2491
+ } catch (error) {
2492
+ logger17.error("Error retrieving with TTL from localStorage", { key, ttl, error });
2493
+ return null;
2494
+ }
2495
+ }
2496
+ set(key, value) {
2497
+ logger17.trace("set", { key, value });
2498
+ if (this.quotaExceeded && this.fallbackCache) {
2499
+ this.fallbackCache.set(key, value);
2500
+ return;
2501
+ }
2502
+ try {
2503
+ const storageKey = this.getStorageKey(key);
2504
+ const toStore = {
2505
+ originalKey: key,
2506
+ value,
2507
+ timestamp: Date.now()
2508
+ };
2509
+ localStorage.setItem(storageKey, JSON.stringify(toStore));
2510
+ if (this.quotaExceeded) {
2511
+ logger17.info("LocalStorage is working again, switching back from fallback cache");
2512
+ this.quotaExceeded = false;
2513
+ }
2514
+ } catch (error) {
2515
+ logger17.error("Error storing to localStorage", { key, value, error });
2516
+ if (this.isQuotaExceededError(error)) {
2517
+ logger17.warning("LocalStorage quota exceeded, attempting cleanup...");
2518
+ const cleanupSucceeded = this.tryCleanupOldEntries();
2519
+ if (cleanupSucceeded) {
2520
+ try {
2521
+ const storageKey = this.getStorageKey(key);
2522
+ const toStore = {
2523
+ originalKey: key,
2524
+ value,
2525
+ timestamp: Date.now()
2526
+ };
2527
+ localStorage.setItem(storageKey, JSON.stringify(toStore));
2528
+ logger17.info("Successfully stored item after cleanup");
2529
+ return;
2530
+ } catch {
2531
+ logger17.warning("Storage failed even after cleanup, falling back to memory cache");
2532
+ }
2533
+ }
2534
+ this.quotaExceeded = true;
2535
+ this.initializeFallbackCache();
2536
+ if (this.fallbackCache) {
2537
+ this.fallbackCache.set(key, value);
2538
+ logger17.info("Item stored in fallback memory cache");
2539
+ }
2540
+ } else {
2541
+ throw new Error(`Failed to store item in localStorage: ${error}`);
2542
+ }
2543
+ }
2544
+ }
2545
+ includesKey(key) {
2546
+ if (this.quotaExceeded && this.fallbackCache) {
2547
+ if (this.fallbackCache.includesKey(key)) {
2548
+ return true;
2549
+ }
2550
+ }
2551
+ try {
2552
+ const storageKey = this.getStorageKey(key);
2553
+ const stored = localStorage.getItem(storageKey);
2554
+ if (stored) {
2555
+ const parsed = JSON.parse(stored);
2556
+ return this.normalizedHashFunction(parsed.originalKey) === this.normalizedHashFunction(key);
2557
+ }
2558
+ if (!this.quotaExceeded && this.fallbackCache) {
2559
+ return this.fallbackCache.includesKey(key);
2560
+ }
2561
+ return false;
2562
+ } catch (error) {
2563
+ logger17.error("Error checking key in localStorage", { key, error });
2564
+ if (this.fallbackCache) {
2565
+ return this.fallbackCache.includesKey(key);
2566
+ }
2567
+ return false;
2568
+ }
2569
+ }
2570
+ delete(key) {
2571
+ logger17.trace("delete", { key });
2572
+ if (this.fallbackCache) {
2573
+ this.fallbackCache.delete(key);
2574
+ }
2575
+ try {
2576
+ const storageKey = this.getStorageKey(key);
2577
+ localStorage.removeItem(storageKey);
2578
+ } catch (error) {
2579
+ logger17.error("Error deleting from localStorage", { key, error });
2580
+ }
2581
+ }
2582
+ allIn(locations) {
2583
+ const allKeys = this.keys();
2584
+ if (locations.length === 0) {
2585
+ logger17.debug("Returning all items, LocKeys is empty");
2586
+ return allKeys.map((key) => this.get(key)).filter((item) => item !== null);
2587
+ } else {
2588
+ const locKeys = locations;
2589
+ logger17.debug("allIn", { locKeys, keys: allKeys.length });
2590
+ return allKeys.filter((key) => key && isComKey3(key)).filter((key) => {
2591
+ const ComKey12 = key;
2592
+ logger17.debug("Comparing Location Keys", {
2593
+ locKeys,
2594
+ ComKey: ComKey12
2595
+ });
2596
+ return isLocKeyArrayEqual(locKeys, ComKey12.loc);
2597
+ }).map((key) => this.get(key));
2598
+ }
2599
+ }
2600
+ contains(query, locations) {
2601
+ logger17.debug("contains", { query, locations });
2602
+ const items = this.allIn(locations);
2603
+ return items.some((item) => isQueryMatch3(item, query));
2604
+ }
2605
+ queryIn(query, locations = []) {
2606
+ logger17.debug("queryIn", { query, locations });
2607
+ const items = this.allIn(locations);
2608
+ return items.filter((item) => isQueryMatch3(item, query));
2609
+ }
2610
+ clone() {
2611
+ return new _LocalStorageCacheMap(this.types, this.keyPrefix);
2612
+ }
2613
+ parseStorageEntry(storageKey) {
2614
+ try {
2615
+ const stored = localStorage.getItem(storageKey);
2616
+ if (stored) {
2617
+ return JSON.parse(stored);
2618
+ }
2619
+ } catch (parseError) {
2620
+ logger17.debug("Skipping corrupted localStorage entry", { storageKey, error: parseError });
2621
+ }
2622
+ return null;
2623
+ }
2624
+ keys() {
2625
+ const keys = [];
2626
+ try {
2627
+ const storageKeys = this.getAllStorageKeys();
2628
+ for (const storageKey of storageKeys) {
2629
+ const parsed = this.parseStorageEntry(storageKey);
2630
+ if (parsed?.originalKey) {
2631
+ keys.push(parsed.originalKey);
2632
+ }
2633
+ }
2634
+ } catch (error) {
2635
+ logger17.error("Error getting keys from localStorage", { error });
2636
+ }
2637
+ return keys;
2638
+ }
2639
+ values() {
2640
+ const values = [];
2641
+ try {
2642
+ const storageKeys = this.getAllStorageKeys();
2643
+ for (const storageKey of storageKeys) {
2644
+ const parsed = this.parseStorageEntry(storageKey);
2645
+ if (parsed?.value) {
2646
+ values.push(parsed.value);
2647
+ }
2648
+ }
2649
+ } catch (error) {
2650
+ logger17.error("Error getting values from localStorage", { error });
2651
+ }
2652
+ return values;
2653
+ }
2654
+ clear() {
2655
+ logger17.debug("Clearing localStorage cache");
2656
+ try {
2657
+ const storageKeys = this.getAllStorageKeys();
2658
+ for (const storageKey of storageKeys) {
2659
+ if (!storageKey.includes(":query:")) {
2660
+ localStorage.removeItem(storageKey);
2661
+ }
2662
+ }
2663
+ } catch (error) {
2664
+ logger17.error("Error clearing localStorage cache", { error });
2665
+ }
2666
+ }
2667
+ // Query result caching methods implementation
2668
+ setQueryResult(queryHash, itemKeys, ttl) {
2669
+ logger17.trace("setQueryResult", { queryHash, itemKeys, ttl });
2670
+ const queryKey = `${this.keyPrefix}:query:${queryHash}`;
2671
+ const entry = {
2672
+ itemKeys
2673
+ };
2674
+ if (ttl) {
2675
+ entry.expiresAt = Date.now() + ttl;
2676
+ }
2677
+ try {
2678
+ localStorage.setItem(queryKey, JSON.stringify(entry));
2679
+ } catch (error) {
2680
+ logger17.error("Failed to store query result in localStorage", { queryHash, error });
2681
+ }
2682
+ }
2683
+ getQueryResult(queryHash) {
2684
+ logger17.trace("getQueryResult", { queryHash });
2685
+ const queryKey = `${this.keyPrefix}:query:${queryHash}`;
2686
+ try {
2687
+ const data = localStorage.getItem(queryKey);
2688
+ if (!data) {
2689
+ return null;
2690
+ }
2691
+ const entry = JSON.parse(data);
2692
+ if (Array.isArray(entry)) {
2693
+ return entry;
2694
+ }
2695
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
2696
+ logger17.trace("Query result expired, removing", { queryHash, expiresAt: entry.expiresAt });
2697
+ localStorage.removeItem(queryKey);
2698
+ return null;
2699
+ }
2700
+ return entry.itemKeys || null;
2701
+ } catch (error) {
2702
+ logger17.error("Failed to retrieve query result from localStorage", { queryHash, error });
2703
+ return null;
2704
+ }
2705
+ }
2706
+ hasQueryResult(queryHash) {
2707
+ return this.getQueryResult(queryHash) !== null;
2708
+ }
2709
+ deleteQueryResult(queryHash) {
2710
+ logger17.trace("deleteQueryResult", { queryHash });
2711
+ const queryKey = `${this.keyPrefix}:query:${queryHash}`;
2712
+ try {
2713
+ localStorage.removeItem(queryKey);
2714
+ } catch (error) {
2715
+ logger17.error("Failed to delete query result from localStorage", { queryHash, error });
2716
+ }
2717
+ }
2718
+ invalidateItemKeys(keys) {
2719
+ logger17.debug("invalidateItemKeys", { keys });
2720
+ keys.forEach((key) => {
2721
+ this.delete(key);
2722
+ });
2723
+ }
2724
+ invalidateLocation(locations) {
2725
+ logger17.debug("invalidateLocation", { locations });
2726
+ if (locations.length === 0) {
2727
+ const allKeys = this.keys();
2728
+ const primaryKeys = allKeys.filter((key) => !isComKey3(key));
2729
+ this.invalidateItemKeys(primaryKeys);
2730
+ } else {
2731
+ const itemsInLocation = this.allIn(locations);
2732
+ const keysToInvalidate = itemsInLocation.map((item) => item.key);
2733
+ this.invalidateItemKeys(keysToInvalidate);
2734
+ }
2735
+ this.clearQueryResults();
2736
+ }
2737
+ clearQueryResults() {
2738
+ logger17.trace("clearQueryResults");
2739
+ const queryPrefix = `${this.keyPrefix}:query:`;
2740
+ try {
2741
+ const keysToRemove = [];
2742
+ for (let i = 0; i < localStorage.length; i++) {
2743
+ const key = localStorage.key(i);
2744
+ if (key && key.startsWith(queryPrefix)) {
2745
+ keysToRemove.push(key);
2746
+ }
2747
+ }
2748
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
2749
+ } catch (error) {
2750
+ logger17.error("Failed to clear query results from localStorage", { error });
2751
+ }
2752
+ }
2753
+ };
2754
+
2755
+ // src/browser/SessionStorageCacheMap.ts
2756
+ import {
2757
+ isComKey as isComKey4,
2758
+ isQueryMatch as isQueryMatch4
2759
+ } from "@fjell/core";
2760
+ var logger18 = logger_default.get("SessionStorageCacheMap");
2761
+ var SessionStorageCacheMap = class _SessionStorageCacheMap extends CacheMap {
2762
+ keyPrefix;
2763
+ normalizedHashFunction;
2764
+ constructor(types, keyPrefix = "fjell-session-cache") {
2765
+ super(types);
2766
+ this.keyPrefix = keyPrefix;
2767
+ this.normalizedHashFunction = createNormalizedHashFunction();
2768
+ }
2769
+ getStorageKey(key) {
2770
+ const hashedKey = this.normalizedHashFunction(key);
2771
+ return `${this.keyPrefix}:${hashedKey}`;
2772
+ }
2773
+ getAllStorageKeys() {
2774
+ const keys = [];
2775
+ for (let i = 0; i < sessionStorage.length; i++) {
2776
+ const key = sessionStorage.key(i);
2777
+ if (key && key.startsWith(`${this.keyPrefix}:`)) {
2778
+ keys.push(key);
2779
+ }
2780
+ }
2781
+ return keys;
2782
+ }
2783
+ get(key) {
2784
+ logger18.trace("get", { key });
2785
+ try {
2786
+ const storageKey = this.getStorageKey(key);
2787
+ const stored = sessionStorage.getItem(storageKey);
2788
+ if (stored) {
2789
+ const parsed = JSON.parse(stored);
2790
+ if (this.normalizedHashFunction(parsed.originalKey) === this.normalizedHashFunction(key)) {
2791
+ return parsed.value;
2792
+ }
2793
+ }
2794
+ return null;
2795
+ } catch (error) {
2796
+ logger18.error("Error retrieving from sessionStorage", { key, error });
2797
+ return null;
2798
+ }
2799
+ }
2800
+ getWithTTL(key, ttl) {
2801
+ logger18.trace("getWithTTL", { key, ttl });
2802
+ if (ttl === 0) {
2803
+ return null;
2804
+ }
2805
+ try {
2806
+ const storageKey = this.getStorageKey(key);
2807
+ const stored = sessionStorage.getItem(storageKey);
2808
+ if (stored) {
2809
+ const parsed = JSON.parse(stored);
2810
+ if (this.normalizedHashFunction(parsed.originalKey) !== this.normalizedHashFunction(key)) {
2811
+ return null;
2812
+ }
2813
+ if (typeof parsed.timestamp === "number" && !isNaN(parsed.timestamp)) {
2814
+ const now = Date.now();
2815
+ const age = now - parsed.timestamp;
2816
+ if (age >= ttl) {
2817
+ logger18.trace("Item expired, removing from sessionStorage", { key, age, ttl });
2818
+ sessionStorage.removeItem(storageKey);
2819
+ return null;
2820
+ }
2821
+ } else {
2822
+ logger18.trace("Item has no valid timestamp, treating as expired", { key });
2823
+ sessionStorage.removeItem(storageKey);
2824
+ return null;
2825
+ }
2826
+ return parsed.value;
2827
+ }
2828
+ return null;
2829
+ } catch (error) {
2830
+ logger18.error("Error retrieving with TTL from sessionStorage", { key, ttl, error });
2831
+ return null;
2832
+ }
2833
+ }
2834
+ set(key, value) {
2835
+ logger18.trace("set", { key, value });
2836
+ try {
2837
+ const storageKey = this.getStorageKey(key);
2838
+ const toStore = {
2839
+ originalKey: key,
2840
+ value,
2841
+ timestamp: Date.now()
2842
+ };
2843
+ sessionStorage.setItem(storageKey, JSON.stringify(toStore));
2844
+ } catch (error) {
2845
+ logger18.error("Error storing to sessionStorage", { key, value, error });
2846
+ throw new Error(`Failed to store item in sessionStorage: ${error}`);
2847
+ }
2848
+ }
2849
+ includesKey(key) {
2850
+ try {
2851
+ const storageKey = this.getStorageKey(key);
2852
+ const stored = sessionStorage.getItem(storageKey);
2853
+ if (stored) {
2854
+ const parsed = JSON.parse(stored);
2855
+ return this.normalizedHashFunction(parsed.originalKey) === this.normalizedHashFunction(key);
2856
+ }
2857
+ return false;
2858
+ } catch (error) {
2859
+ logger18.error("Error checking key in sessionStorage", { key, error });
2860
+ return false;
2861
+ }
2862
+ }
2863
+ delete(key) {
2864
+ logger18.trace("delete", { key });
2865
+ try {
2866
+ const storageKey = this.getStorageKey(key);
2867
+ sessionStorage.removeItem(storageKey);
2868
+ } catch (error) {
2869
+ logger18.error("Error deleting from sessionStorage", { key, error });
2870
+ }
2871
+ }
2872
+ allIn(locations) {
2873
+ const allKeys = this.keys();
2874
+ if (locations.length === 0) {
2875
+ logger18.debug("Returning all items, LocKeys is empty");
2876
+ return allKeys.map((key) => this.get(key)).filter((item) => item !== null);
2877
+ } else {
2878
+ const locKeys = locations;
2879
+ logger18.debug("allIn", { locKeys, keys: allKeys.length });
2880
+ return allKeys.filter((key) => key && isComKey4(key)).filter((key) => {
2881
+ const ComKey12 = key;
2882
+ logger18.debug("Comparing Location Keys", {
2883
+ locKeys,
2884
+ ComKey: ComKey12
2885
+ });
2886
+ return isLocKeyArrayEqual(locKeys, ComKey12.loc);
2887
+ }).map((key) => this.get(key));
2888
+ }
2889
+ }
2890
+ contains(query, locations) {
2891
+ logger18.debug("contains", { query, locations });
2892
+ const items = this.allIn(locations);
2893
+ return items.some((item) => isQueryMatch4(item, query));
2894
+ }
2895
+ queryIn(query, locations = []) {
2896
+ logger18.debug("queryIn", { query, locations });
2897
+ const items = this.allIn(locations);
2898
+ return items.filter((item) => isQueryMatch4(item, query));
2899
+ }
2900
+ clone() {
2901
+ return new _SessionStorageCacheMap(this.types, this.keyPrefix);
2902
+ }
2903
+ keys() {
2904
+ const keys = [];
2905
+ try {
2906
+ const storageKeys = this.getAllStorageKeys();
2907
+ for (const storageKey of storageKeys) {
2908
+ const stored = sessionStorage.getItem(storageKey);
2909
+ if (!stored) continue;
2910
+ try {
2911
+ const parsed = JSON.parse(stored);
2912
+ if (parsed.originalKey) {
2913
+ keys.push(parsed.originalKey);
2914
+ }
2915
+ } catch (itemError) {
2916
+ logger18.trace("Skipping invalid storage item", { storageKey, error: itemError });
2917
+ }
2918
+ }
2919
+ } catch (error) {
2920
+ logger18.error("Error getting keys from sessionStorage", { error });
2921
+ }
2922
+ return keys;
2923
+ }
2924
+ values() {
2925
+ const values = [];
2926
+ try {
2927
+ const storageKeys = this.getAllStorageKeys();
2928
+ for (const storageKey of storageKeys) {
2929
+ const stored = sessionStorage.getItem(storageKey);
2930
+ if (!stored) continue;
2931
+ try {
2932
+ const parsed = JSON.parse(stored);
2933
+ if (parsed.value != null) {
2934
+ values.push(parsed.value);
2935
+ }
2936
+ } catch (itemError) {
2937
+ logger18.trace("Skipping invalid storage item for values", { storageKey, error: itemError });
2938
+ }
2939
+ }
2940
+ } catch (error) {
2941
+ logger18.error("Error getting values from sessionStorage", { error });
2942
+ }
2943
+ return values;
2944
+ }
2945
+ clear() {
2946
+ logger18.debug("Clearing sessionStorage cache");
2947
+ try {
2948
+ const storageKeys = this.getAllStorageKeys();
2949
+ for (const storageKey of storageKeys) {
2950
+ sessionStorage.removeItem(storageKey);
2951
+ }
2952
+ } catch (error) {
2953
+ logger18.error("Error clearing sessionStorage cache", { error });
2954
+ }
2955
+ }
2956
+ // Query result caching methods implementation
2957
+ setQueryResult(queryHash, itemKeys, ttl) {
2958
+ logger18.trace("setQueryResult", { queryHash, itemKeys, ttl });
2959
+ const queryKey = `${this.keyPrefix}:query:${queryHash}`;
2960
+ const entry = {
2961
+ itemKeys
2962
+ };
2963
+ if (ttl) {
2964
+ entry.expiresAt = Date.now() + ttl;
2965
+ }
2966
+ try {
2967
+ sessionStorage.setItem(queryKey, JSON.stringify(entry));
2968
+ } catch (error) {
2969
+ logger18.error("Failed to store query result in sessionStorage", { queryHash, error });
2970
+ }
2971
+ }
2972
+ getQueryResult(queryHash) {
2973
+ logger18.trace("getQueryResult", { queryHash });
2974
+ const queryKey = `${this.keyPrefix}:query:${queryHash}`;
2975
+ try {
2976
+ const data = sessionStorage.getItem(queryKey);
2977
+ if (!data) {
2978
+ return null;
2979
+ }
2980
+ const entry = JSON.parse(data);
2981
+ if (Array.isArray(entry)) {
2982
+ return entry;
2983
+ }
2984
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
2985
+ logger18.trace("Query result expired, removing", { queryHash, expiresAt: entry.expiresAt });
2986
+ sessionStorage.removeItem(queryKey);
2987
+ return null;
2988
+ }
2989
+ return entry.itemKeys || null;
2990
+ } catch (error) {
2991
+ logger18.error("Failed to retrieve query result from sessionStorage", { queryHash, error });
2992
+ return null;
2993
+ }
2994
+ }
2995
+ hasQueryResult(queryHash) {
2996
+ return this.getQueryResult(queryHash) !== null;
2997
+ }
2998
+ deleteQueryResult(queryHash) {
2999
+ logger18.trace("deleteQueryResult", { queryHash });
3000
+ const queryKey = `${this.keyPrefix}:query:${queryHash}`;
3001
+ try {
3002
+ sessionStorage.removeItem(queryKey);
3003
+ } catch (error) {
3004
+ logger18.error("Failed to delete query result from sessionStorage", { queryHash, error });
3005
+ }
3006
+ }
3007
+ invalidateItemKeys(keys) {
3008
+ logger18.debug("invalidateItemKeys", { keys });
3009
+ keys.forEach((key) => {
3010
+ this.delete(key);
3011
+ });
3012
+ }
3013
+ invalidateLocation(locations) {
3014
+ logger18.debug("invalidateLocation", { locations });
3015
+ if (locations.length === 0) {
3016
+ const allKeys = this.keys();
3017
+ const primaryKeys = allKeys.filter((key) => !isComKey4(key));
3018
+ this.invalidateItemKeys(primaryKeys);
3019
+ } else {
3020
+ const itemsInLocation = this.allIn(locations);
3021
+ const keysToInvalidate = itemsInLocation.map((item) => item.key);
3022
+ this.invalidateItemKeys(keysToInvalidate);
3023
+ }
3024
+ this.clearQueryResults();
3025
+ }
3026
+ clearQueryResults() {
3027
+ logger18.trace("clearQueryResults");
3028
+ const queryPrefix = `${this.keyPrefix}:query:`;
3029
+ try {
3030
+ const keysToRemove = [];
3031
+ for (let i = 0; i < sessionStorage.length; i++) {
3032
+ const key = sessionStorage.key(i);
3033
+ if (key && key.startsWith(queryPrefix)) {
3034
+ keysToRemove.push(key);
3035
+ }
3036
+ }
3037
+ keysToRemove.forEach((key) => sessionStorage.removeItem(key));
3038
+ } catch (error) {
3039
+ logger18.error("Failed to clear query results from sessionStorage", { error });
3040
+ }
3041
+ }
3042
+ };
3043
+
3044
+ // src/browser/AsyncIndexDBCacheMap.ts
3045
+ import {
3046
+ isComKey as isComKey5,
3047
+ isQueryMatch as isQueryMatch5
3048
+ } from "@fjell/core";
3049
+ var logger19 = logger_default.get("AsyncIndexDBCacheMap");
3050
+ var AsyncIndexDBCacheMap = class _AsyncIndexDBCacheMap {
3051
+ types;
3052
+ dbName;
3053
+ storeName;
3054
+ version;
3055
+ normalizedHashFunction;
3056
+ dbPromise = null;
3057
+ constructor(types, dbName = "fjell-indexdb-cache", storeName = "cache", version = 1) {
3058
+ this.types = types;
3059
+ this.dbName = dbName;
3060
+ this.storeName = storeName;
3061
+ this.version = version;
3062
+ this.normalizedHashFunction = createNormalizedHashFunction();
3063
+ }
3064
+ async getDB() {
3065
+ if (!this.dbPromise) {
3066
+ this.dbPromise = new Promise((resolve, reject) => {
3067
+ const request = indexedDB.open(this.dbName, this.version);
3068
+ request.onerror = () => {
3069
+ logger19.error("Error opening IndexedDB", { error: request.error });
3070
+ reject(request.error);
3071
+ };
3072
+ request.onsuccess = () => {
3073
+ logger19.debug("IndexedDB opened successfully");
3074
+ resolve(request.result);
3075
+ };
3076
+ request.onupgradeneeded = (event) => {
3077
+ logger19.debug("IndexedDB upgrade needed");
3078
+ const db = event.target.result;
3079
+ if (!db.objectStoreNames.contains(this.storeName)) {
3080
+ db.createObjectStore(this.storeName);
3081
+ logger19.debug("Created object store", { storeName: this.storeName });
3082
+ }
3083
+ };
3084
+ });
3085
+ }
3086
+ return this.dbPromise;
3087
+ }
3088
+ getStorageKey(key) {
3089
+ return this.normalizedHashFunction(key);
3090
+ }
3091
+ async get(key) {
3092
+ logger19.trace("get", { key });
3093
+ try {
3094
+ const db = await this.getDB();
3095
+ const transaction = db.transaction([this.storeName], "readonly");
3096
+ const store = transaction.objectStore(this.storeName);
3097
+ const storageKey = this.getStorageKey(key);
3098
+ return new Promise((resolve, reject) => {
3099
+ const request = store.get(storageKey);
3100
+ request.onerror = () => {
3101
+ logger19.error("Error getting from IndexedDB", { key, error: request.error });
3102
+ reject(request.error);
3103
+ };
3104
+ request.onsuccess = () => {
3105
+ const stored = request.result;
3106
+ if (stored && this.normalizedHashFunction(stored.originalKey) === this.normalizedHashFunction(key)) {
3107
+ resolve(stored.value);
3108
+ } else {
3109
+ resolve(null);
3110
+ }
3111
+ };
3112
+ });
3113
+ } catch (error) {
3114
+ logger19.error("Error in IndexedDB get operation", { key, error });
3115
+ return null;
3116
+ }
3117
+ }
3118
+ async set(key, value) {
3119
+ logger19.trace("set", { key, value });
3120
+ try {
3121
+ const db = await this.getDB();
3122
+ const transaction = db.transaction([this.storeName], "readwrite");
3123
+ const store = transaction.objectStore(this.storeName);
3124
+ const storageKey = this.getStorageKey(key);
3125
+ const storedItem = {
3126
+ originalKey: key,
3127
+ value
3128
+ };
3129
+ return new Promise((resolve, reject) => {
3130
+ const request = store.put(storedItem, storageKey);
3131
+ request.onerror = () => {
3132
+ logger19.error("Error setting in IndexedDB", { key, value, error: request.error });
3133
+ reject(request.error);
3134
+ };
3135
+ request.onsuccess = () => {
3136
+ resolve();
3137
+ };
3138
+ });
3139
+ } catch (error) {
3140
+ logger19.error("Error in IndexedDB set operation", { key, value, error });
3141
+ throw new Error(`Failed to store item in IndexedDB: ${error}`);
3142
+ }
3143
+ }
3144
+ async includesKey(key) {
3145
+ try {
3146
+ const db = await this.getDB();
3147
+ const transaction = db.transaction([this.storeName], "readonly");
3148
+ const store = transaction.objectStore(this.storeName);
3149
+ const storageKey = this.getStorageKey(key);
3150
+ return new Promise((resolve, reject) => {
3151
+ const request = store.get(storageKey);
3152
+ request.onerror = () => {
3153
+ logger19.error("Error checking key in IndexedDB", { key, error: request.error });
3154
+ reject(request.error);
3155
+ };
3156
+ request.onsuccess = () => {
3157
+ const stored = request.result;
3158
+ if (stored) {
3159
+ const matches = this.normalizedHashFunction(stored.originalKey) === this.normalizedHashFunction(key);
3160
+ resolve(matches);
3161
+ } else {
3162
+ resolve(false);
3163
+ }
3164
+ };
3165
+ });
3166
+ } catch (error) {
3167
+ logger19.error("Error in IndexedDB includesKey operation", { key, error });
3168
+ return false;
3169
+ }
3170
+ }
3171
+ async delete(key) {
3172
+ logger19.trace("delete", { key });
3173
+ try {
3174
+ const db = await this.getDB();
3175
+ const transaction = db.transaction([this.storeName], "readwrite");
3176
+ const store = transaction.objectStore(this.storeName);
3177
+ const storageKey = this.getStorageKey(key);
3178
+ return new Promise((resolve, reject) => {
3179
+ const request = store.delete(storageKey);
3180
+ request.onerror = () => {
3181
+ logger19.error("Error deleting from IndexedDB", { key, error: request.error });
3182
+ reject(request.error);
3183
+ };
3184
+ request.onsuccess = () => {
3185
+ resolve();
3186
+ };
3187
+ });
3188
+ } catch (error) {
3189
+ logger19.error("Error in IndexedDB delete operation", { key, error });
3190
+ }
3191
+ }
3192
+ async allIn(locations) {
3193
+ const allKeys = await this.keys();
3194
+ if (locations.length === 0) {
3195
+ logger19.debug("Returning all items, LocKeys is empty");
3196
+ const promises = allKeys.map((key) => this.get(key));
3197
+ const results = await Promise.all(promises);
3198
+ return results.filter((item) => item !== null);
3199
+ } else {
3200
+ const locKeys = locations;
3201
+ logger19.debug("allIn", { locKeys, keys: allKeys.length });
3202
+ const filteredKeys = allKeys.filter((key) => key && isComKey5(key)).filter((key) => {
3203
+ const ComKey12 = key;
3204
+ logger19.debug("Comparing Location Keys", {
3205
+ locKeys,
3206
+ ComKey: ComKey12
3207
+ });
3208
+ return isLocKeyArrayEqual(locKeys, ComKey12.loc);
3209
+ });
3210
+ const promises = filteredKeys.map((key) => this.get(key));
3211
+ const results = await Promise.all(promises);
3212
+ return results.filter((item) => item !== null);
3213
+ }
3214
+ }
3215
+ async contains(query, locations) {
3216
+ logger19.debug("contains", { query, locations });
3217
+ const items = await this.allIn(locations);
3218
+ return items.some((item) => isQueryMatch5(item, query));
3219
+ }
3220
+ async queryIn(query, locations = []) {
3221
+ logger19.debug("queryIn", { query, locations });
3222
+ const items = await this.allIn(locations);
3223
+ return items.filter((item) => isQueryMatch5(item, query));
3224
+ }
3225
+ clone() {
3226
+ return new _AsyncIndexDBCacheMap(this.types, this.dbName, this.storeName, this.version);
3227
+ }
3228
+ async keys() {
3229
+ const keys = [];
3230
+ try {
3231
+ const db = await this.getDB();
3232
+ const transaction = db.transaction([this.storeName], "readonly");
3233
+ const store = transaction.objectStore(this.storeName);
3234
+ return new Promise((resolve, reject) => {
3235
+ const request = store.openCursor();
3236
+ request.onerror = () => {
3237
+ logger19.error("Error getting keys from IndexedDB", { error: request.error });
3238
+ reject(request.error);
3239
+ };
3240
+ request.onsuccess = (event) => {
3241
+ const cursor = event.target.result;
3242
+ if (cursor) {
3243
+ const stored = cursor.value;
3244
+ keys.push(stored.originalKey);
3245
+ cursor.continue();
3246
+ } else {
3247
+ resolve(keys);
3248
+ }
3249
+ };
3250
+ });
3251
+ } catch (error) {
3252
+ logger19.error("Error in IndexedDB keys operation", { error });
3253
+ return [];
3254
+ }
3255
+ }
3256
+ async values() {
3257
+ const values = [];
3258
+ try {
3259
+ const db = await this.getDB();
3260
+ const transaction = db.transaction([this.storeName], "readonly");
3261
+ const store = transaction.objectStore(this.storeName);
3262
+ return new Promise((resolve, reject) => {
3263
+ const request = store.openCursor();
3264
+ request.onerror = () => {
3265
+ logger19.error("Error getting values from IndexedDB", { error: request.error });
3266
+ reject(request.error);
3267
+ };
3268
+ request.onsuccess = (event) => {
3269
+ const cursor = event.target.result;
3270
+ if (cursor) {
3271
+ const stored = cursor.value;
3272
+ values.push(stored.value);
3273
+ cursor.continue();
3274
+ } else {
3275
+ resolve(values);
3276
+ }
3277
+ };
3278
+ });
3279
+ } catch (error) {
3280
+ logger19.error("Error in IndexedDB values operation", { error });
3281
+ return [];
3282
+ }
3283
+ }
3284
+ async clear() {
3285
+ logger19.debug("Clearing IndexedDB cache");
3286
+ try {
3287
+ const db = await this.getDB();
3288
+ const transaction = db.transaction([this.storeName], "readwrite");
3289
+ const store = transaction.objectStore(this.storeName);
3290
+ return new Promise((resolve, reject) => {
3291
+ const request = store.clear();
3292
+ request.onerror = () => {
3293
+ logger19.error("Error clearing IndexedDB cache", { error: request.error });
3294
+ reject(request.error);
3295
+ };
3296
+ request.onsuccess = () => {
3297
+ resolve();
3298
+ };
3299
+ });
3300
+ } catch (error) {
3301
+ logger19.error("Error in IndexedDB clear operation", { error });
3302
+ }
3303
+ }
3304
+ // Async Query result caching methods
3305
+ async setQueryResult(queryHash, itemKeys, ttl) {
3306
+ logger19.trace("setQueryResult", { queryHash, itemKeys, ttl });
3307
+ try {
3308
+ return new Promise((resolve, reject) => {
3309
+ const request = indexedDB.open(this.dbName, this.version);
3310
+ request.onerror = () => {
3311
+ logger19.error("Failed to open database for setQueryResult", { error: request.error });
3312
+ reject(request.error);
3313
+ };
3314
+ request.onsuccess = () => {
3315
+ const db = request.result;
3316
+ const transaction = db.transaction([this.storeName], "readwrite");
3317
+ const store = transaction.objectStore(this.storeName);
3318
+ const entry = {
3319
+ itemKeys,
3320
+ expiresAt: ttl ? Date.now() + ttl : null
3321
+ };
3322
+ const queryKey = `query:${queryHash}`;
3323
+ const putRequest = store.put(JSON.stringify(entry), queryKey);
3324
+ putRequest.onerror = () => {
3325
+ logger19.error("Failed to store query result", { queryHash, error: putRequest.error });
3326
+ reject(putRequest.error);
3327
+ };
3328
+ putRequest.onsuccess = () => {
3329
+ resolve();
3330
+ };
3331
+ };
3332
+ });
3333
+ } catch (error) {
3334
+ logger19.error("Error in setQueryResult", { queryHash, error });
3335
+ throw error;
3336
+ }
3337
+ }
3338
+ async getQueryResult(queryHash) {
3339
+ logger19.trace("getQueryResult", { queryHash });
3340
+ try {
3341
+ return new Promise((resolve, reject) => {
3342
+ const request = indexedDB.open(this.dbName, this.version);
3343
+ request.onerror = () => {
3344
+ logger19.error("Failed to open database for getQueryResult", { error: request.error });
3345
+ reject(request.error);
3346
+ };
3347
+ request.onsuccess = () => {
3348
+ const db = request.result;
3349
+ const transaction = db.transaction([this.storeName], "readonly");
3350
+ const store = transaction.objectStore(this.storeName);
3351
+ const queryKey = `query:${queryHash}`;
3352
+ const getRequest = store.get(queryKey);
3353
+ getRequest.onerror = () => {
3354
+ logger19.error("Failed to retrieve query result", { queryHash, error: getRequest.error });
3355
+ reject(getRequest.error);
3356
+ };
3357
+ getRequest.onsuccess = () => {
3358
+ try {
3359
+ const result = getRequest.result;
3360
+ if (!result) {
3361
+ resolve(null);
3362
+ return;
3363
+ }
3364
+ const entry = JSON.parse(result);
3365
+ if (Array.isArray(entry)) {
3366
+ resolve(entry);
3367
+ return;
3368
+ }
3369
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
3370
+ logger19.trace("Query result expired, removing", { queryHash, expiresAt: entry.expiresAt });
3371
+ const deleteTransaction = db.transaction([this.storeName], "readwrite");
3372
+ const deleteStore = deleteTransaction.objectStore(this.storeName);
3373
+ deleteStore.delete(queryKey);
3374
+ resolve(null);
3375
+ return;
3376
+ }
3377
+ resolve(entry.itemKeys || null);
3378
+ } catch (parseError) {
3379
+ logger19.error("Failed to parse query result", { queryHash, error: parseError });
3380
+ resolve(null);
3381
+ }
3382
+ };
3383
+ };
3384
+ });
3385
+ } catch (error) {
3386
+ logger19.error("Error in getQueryResult", { queryHash, error });
3387
+ return null;
3388
+ }
3389
+ }
3390
+ async hasQueryResult(queryHash) {
3391
+ logger19.trace("hasQueryResult", { queryHash });
3392
+ try {
3393
+ const result = await this.getQueryResult(queryHash);
3394
+ return result !== null;
3395
+ } catch (error) {
3396
+ logger19.error("Error in hasQueryResult", { queryHash, error });
3397
+ return false;
3398
+ }
3399
+ }
3400
+ async deleteQueryResult(queryHash) {
3401
+ logger19.trace("deleteQueryResult", { queryHash });
3402
+ try {
3403
+ return new Promise((resolve, reject) => {
3404
+ const request = indexedDB.open(this.dbName, this.version);
3405
+ request.onerror = () => {
3406
+ logger19.error("Failed to open database for deleteQueryResult", { error: request.error });
3407
+ reject(request.error);
3408
+ };
3409
+ request.onsuccess = () => {
3410
+ const db = request.result;
3411
+ const transaction = db.transaction([this.storeName], "readwrite");
3412
+ const store = transaction.objectStore(this.storeName);
3413
+ const queryKey = `query:${queryHash}`;
3414
+ const deleteRequest = store.delete(queryKey);
3415
+ deleteRequest.onerror = () => {
3416
+ logger19.error("Failed to delete query result", { queryHash, error: deleteRequest.error });
3417
+ reject(deleteRequest.error);
3418
+ };
3419
+ deleteRequest.onsuccess = () => {
3420
+ resolve();
3421
+ };
3422
+ };
3423
+ });
3424
+ } catch (error) {
3425
+ logger19.error("Error in deleteQueryResult", { queryHash, error });
3426
+ throw error;
3427
+ }
3428
+ }
3429
+ async invalidateItemKeys(keys) {
3430
+ logger19.debug("invalidateItemKeys", { keys });
3431
+ for (const key of keys) {
3432
+ await this.delete(key);
3433
+ }
3434
+ }
3435
+ async invalidateLocation(locations) {
3436
+ logger19.debug("invalidateLocation", { locations });
3437
+ if (locations.length === 0) {
3438
+ await this.clearQueryResults();
3439
+ } else {
3440
+ const itemsInLocation = await this.allIn(locations);
3441
+ const keysToInvalidate = itemsInLocation.map((item) => item.key);
3442
+ await this.invalidateItemKeys(keysToInvalidate);
3443
+ }
3444
+ await this.clearQueryResults();
3445
+ }
3446
+ async clearQueryResults() {
3447
+ logger19.trace("clearQueryResults");
3448
+ try {
3449
+ return new Promise((resolve, reject) => {
3450
+ const request = indexedDB.open(this.dbName, this.version);
3451
+ request.onerror = () => {
3452
+ logger19.error("Failed to open database for clearQueryResults", { error: request.error });
3453
+ reject(request.error);
3454
+ };
3455
+ request.onsuccess = () => {
3456
+ const db = request.result;
3457
+ const transaction = db.transaction([this.storeName], "readwrite");
3458
+ const store = transaction.objectStore(this.storeName);
3459
+ const cursorRequest = store.openCursor();
3460
+ const keysToDelete = [];
3461
+ cursorRequest.onerror = () => {
3462
+ logger19.error("Failed to open cursor for clearQueryResults", { error: cursorRequest.error });
3463
+ reject(cursorRequest.error);
3464
+ };
3465
+ cursorRequest.onsuccess = () => {
3466
+ const cursor = cursorRequest.result;
3467
+ if (cursor) {
3468
+ const key = cursor.key;
3469
+ if (typeof key === "string" && key.startsWith("query:")) {
3470
+ keysToDelete.push(key);
3471
+ }
3472
+ cursor.continue();
3473
+ } else {
3474
+ if (keysToDelete.length === 0) {
3475
+ resolve();
3476
+ return;
3477
+ }
3478
+ let deletedCount = 0;
3479
+ const totalToDelete = keysToDelete.length;
3480
+ keysToDelete.forEach((queryKey) => {
3481
+ const deleteRequest = store.delete(queryKey);
3482
+ deleteRequest.onerror = () => {
3483
+ logger19.error("Failed to delete query key", { queryKey, error: deleteRequest.error });
3484
+ deletedCount++;
3485
+ if (deletedCount === totalToDelete) {
3486
+ resolve();
3487
+ }
3488
+ };
3489
+ deleteRequest.onsuccess = () => {
3490
+ deletedCount++;
3491
+ if (deletedCount === totalToDelete) {
3492
+ resolve();
3493
+ }
3494
+ };
3495
+ });
3496
+ }
3497
+ };
3498
+ };
3499
+ });
3500
+ } catch (error) {
3501
+ logger19.error("Error in clearQueryResults", { error });
3502
+ throw error;
3503
+ }
3504
+ }
3505
+ };
3506
+
3507
+ // src/browser/IndexDBCacheMap.ts
3508
+ var IndexDBCacheMap = class _IndexDBCacheMap extends CacheMap {
3509
+ asyncCache;
3510
+ memoryCache;
3511
+ syncInterval = null;
3512
+ SYNC_INTERVAL_MS = 5e3;
3513
+ // Sync every 5 seconds
3514
+ pendingSyncOperations = /* @__PURE__ */ new Map();
3515
+ initializationPromise = null;
3516
+ isInitialized = false;
3517
+ MAX_RETRY_ATTEMPTS = 3;
3518
+ constructor(types, dbName = "fjell-indexdb-cache", storeName = "cache", version = 1) {
3519
+ super(types);
3520
+ this.asyncCache = new AsyncIndexDBCacheMap(types, dbName, storeName, version);
3521
+ this.memoryCache = new MemoryCacheMap(types);
3522
+ this.initializeFromIndexedDB();
3523
+ this.startPeriodicSync();
3524
+ }
3525
+ async initializeFromIndexedDB() {
3526
+ if (this.initializationPromise) {
3527
+ return this.initializationPromise;
3528
+ }
3529
+ this.initializationPromise = (async () => {
3530
+ try {
3531
+ const keys = await this.asyncCache.keys();
3532
+ for (const key of keys) {
3533
+ if (!this.memoryCache.includesKey(key)) {
3534
+ const value = await this.asyncCache.get(key);
3535
+ if (value) {
3536
+ this.memoryCache.set(key, value);
3537
+ }
3538
+ }
3539
+ }
3540
+ this.isInitialized = true;
3541
+ } catch (error) {
3542
+ console.warn("Failed to initialize from IndexedDB:", error);
3543
+ this.isInitialized = true;
3544
+ }
3545
+ })();
3546
+ return this.initializationPromise;
3547
+ }
3548
+ startPeriodicSync() {
3549
+ this.syncInterval = setInterval(() => {
3550
+ this.syncToIndexedDB();
3551
+ }, this.SYNC_INTERVAL_MS);
3552
+ }
3553
+ async syncToIndexedDB() {
3554
+ try {
3555
+ await this.processPendingOperations();
3556
+ const memoryKeys = this.memoryCache.keys();
3557
+ for (const key of memoryKeys) {
3558
+ const value = this.memoryCache.get(key);
3559
+ if (value) {
3560
+ await this.asyncCache.set(key, value);
3561
+ }
3562
+ }
3563
+ } catch (error) {
3564
+ console.warn("Failed to sync to IndexedDB:", error);
3565
+ }
3566
+ }
3567
+ async processPendingOperations() {
3568
+ const pendingOps = Array.from(this.pendingSyncOperations.entries());
3569
+ for (const [keyStr, operation] of pendingOps) {
3570
+ try {
3571
+ if (operation.type === "set" && operation.value) {
3572
+ await this.asyncCache.set(operation.key, operation.value);
3573
+ } else if (operation.type === "delete") {
3574
+ await this.asyncCache.delete(operation.key);
3575
+ }
3576
+ this.pendingSyncOperations.delete(keyStr);
3577
+ } catch (error) {
3578
+ console.warn(`Failed to process pending ${operation.type} operation:`, error);
3579
+ }
3580
+ }
3581
+ }
3582
+ queueForSync(key, value) {
3583
+ const keyStr = JSON.stringify(key);
3584
+ this.pendingSyncOperations.set(keyStr, { type: "set", key, value });
3585
+ setTimeout(async () => {
3586
+ try {
3587
+ await this.asyncCache.set(key, value);
3588
+ const pending = this.pendingSyncOperations.get(keyStr);
3589
+ if (pending && pending.type === "set" && pending.value === value) {
3590
+ this.pendingSyncOperations.delete(keyStr);
3591
+ }
3592
+ } catch (error) {
3593
+ console.warn("Failed to sync single operation to IndexedDB:", error);
3594
+ }
3595
+ }, 0);
3596
+ }
3597
+ queueDeleteForSync(key) {
3598
+ const keyStr = JSON.stringify(key);
3599
+ this.pendingSyncOperations.set(keyStr, { type: "delete", key });
3600
+ setTimeout(async () => {
3601
+ try {
3602
+ await this.asyncCache.delete(key);
3603
+ const pending = this.pendingSyncOperations.get(keyStr);
3604
+ if (pending && pending.type === "delete") {
3605
+ this.pendingSyncOperations.delete(keyStr);
3606
+ }
3607
+ } catch (error) {
3608
+ console.warn("Failed to sync delete operation to IndexedDB:", error);
3609
+ }
3610
+ }, 0);
3611
+ }
3612
+ queueClearForSync() {
3613
+ this.pendingSyncOperations.clear();
3614
+ setTimeout(async () => {
3615
+ try {
3616
+ await this.asyncCache.clear();
3617
+ } catch (error) {
3618
+ console.warn("Failed to sync clear operation to IndexedDB:", error);
3619
+ }
3620
+ }, 0);
3621
+ }
3622
+ get(key) {
3623
+ if (!this.isInitialized && this.initializationPromise) {
3624
+ }
3625
+ return this.memoryCache.get(key);
3626
+ }
3627
+ getWithTTL(key, ttl) {
3628
+ return this.memoryCache.getWithTTL(key, ttl);
3629
+ }
3630
+ set(key, value) {
3631
+ this.memoryCache.set(key, value);
3632
+ this.queueForSync(key, value);
3633
+ }
3634
+ includesKey(key) {
3635
+ return this.memoryCache.includesKey(key);
3636
+ }
3637
+ delete(key) {
3638
+ this.memoryCache.delete(key);
3639
+ this.queueDeleteForSync(key);
3640
+ }
3641
+ allIn(locations) {
3642
+ return this.memoryCache.allIn(locations);
3643
+ }
3644
+ contains(query, locations) {
3645
+ return this.memoryCache.contains(query, locations);
3646
+ }
3647
+ queryIn(query, locations) {
3648
+ return this.memoryCache.queryIn(query, locations);
3649
+ }
3650
+ clone() {
3651
+ return new _IndexDBCacheMap(this.types);
3652
+ }
3653
+ keys() {
3654
+ return this.memoryCache.keys();
3655
+ }
3656
+ values() {
3657
+ return this.memoryCache.values();
3658
+ }
3659
+ clear() {
3660
+ this.memoryCache.clear();
3661
+ this.queueClearForSync();
3662
+ }
3663
+ // Query result caching methods implementation
3664
+ setQueryResult(queryHash, itemKeys, ttl) {
3665
+ return this.memoryCache.setQueryResult(queryHash, itemKeys, ttl);
3666
+ }
3667
+ getQueryResult(queryHash) {
3668
+ return this.memoryCache.getQueryResult(queryHash);
3669
+ }
3670
+ hasQueryResult(queryHash) {
3671
+ return this.memoryCache.hasQueryResult(queryHash);
3672
+ }
3673
+ deleteQueryResult(queryHash) {
3674
+ return this.memoryCache.deleteQueryResult(queryHash);
3675
+ }
3676
+ invalidateItemKeys(keys) {
3677
+ return this.memoryCache.invalidateItemKeys(keys);
3678
+ }
3679
+ invalidateLocation(locations) {
3680
+ return this.memoryCache.invalidateLocation(locations);
3681
+ }
3682
+ clearQueryResults() {
3683
+ return this.memoryCache.clearQueryResults();
3684
+ }
3685
+ /**
3686
+ * Clean up resources when the cache is no longer needed
3687
+ */
3688
+ destroy() {
3689
+ if (this.syncInterval) {
3690
+ clearInterval(this.syncInterval);
3691
+ this.syncInterval = null;
3692
+ }
3693
+ }
3694
+ };
3695
+
3696
+ // src/Options.ts
3697
+ var DEFAULT_CACHE_OPTIONS = {
3698
+ cacheType: "memory",
3699
+ enableDebugLogging: false,
3700
+ autoSync: true,
3701
+ maxRetries: 3,
3702
+ retryDelay: 1e3,
3703
+ indexedDBConfig: {
3704
+ dbName: "fjell-cache",
3705
+ version: 1,
3706
+ storeName: "cache",
3707
+ size: {
3708
+ evictionPolicy: "lru"
3709
+ }
3710
+ },
3711
+ webStorageConfig: {
3712
+ keyPrefix: "fjell-cache:",
3713
+ compress: false,
3714
+ size: {
3715
+ evictionPolicy: "lru"
3716
+ }
3717
+ },
3718
+ memoryConfig: {
3719
+ // No limits by default
3720
+ size: {
3721
+ evictionPolicy: "lru"
3722
+ }
3723
+ }
3724
+ };
3725
+ var createOptions = (cacheOptions) => {
3726
+ const indexedDBConfig = cacheOptions?.indexedDBConfig ? {
3727
+ ...DEFAULT_CACHE_OPTIONS.indexedDBConfig,
3728
+ ...cacheOptions.indexedDBConfig,
3729
+ size: cacheOptions.indexedDBConfig.size ? {
3730
+ ...DEFAULT_CACHE_OPTIONS.indexedDBConfig?.size,
3731
+ ...cacheOptions.indexedDBConfig.size
3732
+ } : DEFAULT_CACHE_OPTIONS.indexedDBConfig?.size
3733
+ } : { ...DEFAULT_CACHE_OPTIONS.indexedDBConfig };
3734
+ const webStorageConfig = cacheOptions?.webStorageConfig ? {
3735
+ ...DEFAULT_CACHE_OPTIONS.webStorageConfig,
3736
+ ...cacheOptions.webStorageConfig,
3737
+ size: cacheOptions.webStorageConfig.size ? {
3738
+ ...DEFAULT_CACHE_OPTIONS.webStorageConfig?.size,
3739
+ ...cacheOptions.webStorageConfig.size
3740
+ } : DEFAULT_CACHE_OPTIONS.webStorageConfig?.size
3741
+ } : { ...DEFAULT_CACHE_OPTIONS.webStorageConfig };
3742
+ const memoryConfig = cacheOptions?.memoryConfig ? {
3743
+ ...DEFAULT_CACHE_OPTIONS.memoryConfig,
3744
+ ...cacheOptions.memoryConfig,
3745
+ size: cacheOptions.memoryConfig.size ? {
3746
+ ...DEFAULT_CACHE_OPTIONS.memoryConfig?.size,
3747
+ ...cacheOptions.memoryConfig.size
3748
+ } : DEFAULT_CACHE_OPTIONS.memoryConfig?.size
3749
+ } : { ...DEFAULT_CACHE_OPTIONS.memoryConfig };
3750
+ return {
3751
+ ...DEFAULT_CACHE_OPTIONS,
3752
+ ...cacheOptions,
3753
+ indexedDBConfig,
3754
+ webStorageConfig,
3755
+ memoryConfig
3756
+ };
3757
+ };
3758
+ var createCacheMap = (kta, options) => {
3759
+ switch (options.cacheType) {
3760
+ case "memory":
3761
+ if (options.memoryConfig?.size && (options.memoryConfig.size.maxSizeBytes || options.memoryConfig.size.maxItems)) {
3762
+ return new EnhancedMemoryCacheMap(
3763
+ kta,
3764
+ options.memoryConfig.size
3765
+ );
3766
+ }
3767
+ return new MemoryCacheMap(kta);
3768
+ case "localStorage":
3769
+ return new LocalStorageCacheMap(
3770
+ kta,
3771
+ options.webStorageConfig?.keyPrefix
3772
+ );
3773
+ case "sessionStorage":
3774
+ return new SessionStorageCacheMap(
3775
+ kta,
3776
+ options.webStorageConfig?.keyPrefix
3777
+ );
3778
+ case "indexedDB":
3779
+ return new IndexDBCacheMap(
3780
+ kta,
3781
+ options.indexedDBConfig?.dbName,
3782
+ options.indexedDBConfig?.storeName,
3783
+ options.indexedDBConfig?.version
3784
+ );
3785
+ case "custom":
3786
+ if (!options.customCacheMapFactory) {
3787
+ throw new Error('Custom cache map factory is required when cacheType is "custom"');
3788
+ }
3789
+ return options.customCacheMapFactory(kta);
3790
+ default:
3791
+ throw new Error(`Unsupported cache type: ${options.cacheType}`);
3792
+ }
3793
+ };
3794
+ var validateOptions = (options) => {
3795
+ if (options.cacheType === "custom" && !options.customCacheMapFactory) {
3796
+ throw new Error('customCacheMapFactory is required when cacheType is "custom"');
3797
+ }
3798
+ if (typeof options.maxRetries === "number" && options.maxRetries < 0) {
3799
+ throw new Error("maxRetries must be non-negative");
3800
+ }
3801
+ if (typeof options.retryDelay === "number" && options.retryDelay < 0) {
3802
+ throw new Error("retryDelay must be non-negative");
3803
+ }
3804
+ if (typeof options.ttl === "number" && options.ttl <= 0) {
3805
+ throw new Error("ttl must be positive");
3806
+ }
3807
+ if (typeof options.memoryConfig?.maxItems === "number" && options.memoryConfig.maxItems <= 0) {
3808
+ throw new Error("memoryConfig.maxItems must be positive");
3809
+ }
3810
+ if (typeof options.memoryConfig?.ttl === "number" && options.memoryConfig.ttl <= 0) {
3811
+ throw new Error("memoryConfig.ttl must be positive");
3812
+ }
3813
+ if (options.memoryConfig?.size) {
3814
+ validateSizeConfig(options.memoryConfig.size);
3815
+ }
3816
+ if (options.webStorageConfig?.size) {
3817
+ validateSizeConfig(options.webStorageConfig.size);
3818
+ }
3819
+ if (options.indexedDBConfig?.size) {
3820
+ validateSizeConfig(options.indexedDBConfig.size);
3821
+ }
3822
+ if (["localStorage", "sessionStorage"].includes(options.cacheType)) {
3823
+ if (typeof window === "undefined" || !window[options.cacheType]) {
3824
+ throw new Error(`${options.cacheType} is not available in non-browser environments`);
3825
+ }
3826
+ }
3827
+ if (options.cacheType === "indexedDB") {
3828
+ if (typeof window === "undefined" || !window.indexedDB) {
3829
+ throw new Error(`${options.cacheType} is not available in this environment`);
3830
+ }
3831
+ }
3832
+ if (options.cacheType === "asyncIndexedDB") {
3833
+ throw new Error("asyncIndexedDB cannot be used with synchronous cache factory. Use AsyncIndexDBCacheMap directly for async operations.");
3834
+ }
3835
+ };
3836
+
3837
+ // src/ops/reset.ts
3838
+ var reset = async (coordinate, options) => {
3839
+ const cacheMap = createCacheMap(coordinate.kta, options);
3840
+ return [cacheMap];
3841
+ };
3842
+
3843
+ // src/Operations.ts
3844
+ var createOperations = (api, coordinate, cacheMap, pkType, options) => {
3845
+ const context = createCacheContext(api, cacheMap, pkType, options);
3846
+ return {
3847
+ all: (query, locations) => all(query, locations, context).then(([ctx, result]) => [ctx.cacheMap, result]),
3848
+ one: (query, locations) => one(query, locations, context).then(([ctx, result]) => [ctx.cacheMap, result]),
3849
+ create: (item, locations) => create(item, locations, context).then(([ctx, result]) => [ctx.cacheMap, result]),
3850
+ get: (key) => get(key, context).then(([ctx, result]) => [ctx.cacheMap, result]),
3851
+ retrieve: (key) => retrieve(key, context).then(([ctx, result]) => [ctx ? ctx.cacheMap : null, result]),
3852
+ remove: (key) => remove(key, context).then((ctx) => ctx.cacheMap),
3853
+ update: (key, item) => update(key, item, context).then(([ctx, result]) => [ctx.cacheMap, result]),
3854
+ action: (key, actionName, body) => action(key, actionName, body, context).then(([ctx, result]) => [ctx.cacheMap, result]),
3855
+ allAction: (actionName, body, locations) => allAction(actionName, body, locations, context).then(([ctx, result]) => [ctx.cacheMap, result]),
3856
+ facet: (key, facetName, params) => facet(key, facetName, params, context).then((result) => [context.cacheMap, result]),
3857
+ allFacet: (facetName, params, locations) => allFacet(facetName, params, locations, context).then((result) => [context.cacheMap, result]),
3858
+ find: (finder, params, locations) => find(finder, params, locations, context).then(([ctx, result]) => [ctx.cacheMap, result]),
3859
+ findOne: (finder, params, locations) => findOne(finder, params, locations, context).then(([ctx, result]) => [ctx.cacheMap, result]),
3860
+ set: (key, item) => set(key, item, context).then(([ctx, result]) => [ctx.cacheMap, result]),
3861
+ reset: () => reset(coordinate, options)
3862
+ };
3863
+ };
3864
+
3865
+ // src/Cache.ts
3866
+ var logger20 = logger_default.get("Cache");
3867
+ var createCache = (api, coordinate, registry, options) => {
3868
+ logger20.debug("createCache", { coordinate, registry, options });
3869
+ const completeOptions = createOptions(options);
3870
+ const cacheMap = createCacheMap(coordinate.kta, completeOptions);
3871
+ const pkType = coordinate.kta[0];
3872
+ const operations = createOperations(api, coordinate, cacheMap, pkType, completeOptions);
3873
+ return {
3874
+ coordinate,
3875
+ registry,
3876
+ api,
3877
+ cacheMap,
3878
+ operations,
3879
+ options: completeOptions
3880
+ };
3881
+ };
3882
+ var isCache = (cache) => {
3883
+ return cache !== null && typeof cache === "object" && "coordinate" in cache && "registry" in cache && "api" in cache && "cacheMap" in cache && "operations" in cache;
3884
+ };
3885
+
3886
+ // src/InstanceFactory.ts
3887
+ var logger21 = logger_default.get("InstanceFactory");
3888
+ var createInstanceFactory = (api, options) => {
3889
+ const templateOptions = createOptions(options);
3890
+ validateOptions(templateOptions);
3891
+ return (coordinate, context) => {
3892
+ const instanceOptions = createOptions(options);
3893
+ logger21.debug("Creating cache instance", {
3894
+ coordinate,
3895
+ registry: context.registry,
3896
+ api,
3897
+ cacheType: instanceOptions.cacheType,
3898
+ options: instanceOptions
3899
+ });
3900
+ const cacheMap = createCacheMap(coordinate.kta, instanceOptions);
3901
+ const pkType = coordinate.kta[0];
3902
+ const operations = createOperations(api, coordinate, cacheMap, pkType, instanceOptions);
3903
+ return {
3904
+ coordinate,
3905
+ registry: context.registry,
3906
+ api,
3907
+ cacheMap,
3908
+ operations,
3909
+ options: instanceOptions
3910
+ };
3911
+ };
3912
+ };
3913
+
3914
+ // src/Instance.ts
3915
+ var logger22 = logger_default.get("Instance");
3916
+ var createInstance = (registry, coordinate, api, options) => {
3917
+ logger22.debug("createInstance", { coordinate, api, registry, options });
3918
+ return createCache(api, coordinate, registry, options);
3919
+ };
3920
+ var isInstance = (instance) => {
3921
+ return instance !== null && typeof instance === "object" && "coordinate" in instance && "registry" in instance && "api" in instance && "cacheMap" in instance && "operations" in instance;
3922
+ };
3923
+
3924
+ // src/Aggregator.ts
3925
+ var logger23 = logger_default.get("ItemAggregator");
3926
+ var toCacheConfig = (config) => {
3927
+ let cacheConfig;
3928
+ if (config.optional === void 0) {
3929
+ cacheConfig = { cache: config, optional: false };
3930
+ } else {
3931
+ cacheConfig = config;
3932
+ }
3933
+ return cacheConfig;
3934
+ };
3935
+ var createAggregator = async (cache, { aggregates = {}, events = {} }) => {
3936
+ const populate = async (item) => {
3937
+ logger23.default("populate", { item });
3938
+ for (const key in aggregates) {
3939
+ await populateAggregate(key, item);
3940
+ }
3941
+ for (const key in events) {
3942
+ await populateEvent(key, item);
3943
+ }
3944
+ logger23.default("populate done", { item });
3945
+ return item;
3946
+ };
3947
+ const populateAggregate = async (key, item) => {
3948
+ logger23.default("populate aggregate key", { key });
3949
+ const cacheConfig = toCacheConfig(aggregates[key]);
3950
+ if (item.refs === void 0) {
3951
+ if (cacheConfig.optional === false) {
3952
+ logger23.error("Item does not have refs an is not optional " + JSON.stringify(item));
3953
+ throw new Error("Item does not have refs an is not optional " + JSON.stringify(item));
3954
+ } else {
3955
+ if (item.events && Object.prototype.hasOwnProperty.call(item.events, key)) {
3956
+ delete item.events[key];
3957
+ }
3958
+ }
3959
+ } else if (item.refs[key] === void 0) {
3960
+ if (cacheConfig.optional === false) {
3961
+ logger23.error("Item does not have mandatory ref with key, not optional " + key + " " + JSON.stringify(item));
3962
+ throw new Error("Item does not have mandatory ref with key, not optional " + key + " " + JSON.stringify(item));
3963
+ } else {
3964
+ if (item.events && Object.prototype.hasOwnProperty.call(item.events, key)) {
3965
+ delete item.events[key];
3966
+ }
3967
+ }
3968
+ } else {
3969
+ const ref = item.refs[key];
3970
+ logger23.default("AGG Retrieving Item in Populate", { key: ref });
3971
+ const [, newItem] = await cacheConfig.cache.operations.retrieve(ref);
3972
+ if (newItem) {
3973
+ if (item.aggs === void 0) {
3974
+ item.aggs = {};
3975
+ }
3976
+ item.aggs[key] = {
3977
+ key: ref,
3978
+ item: newItem
3979
+ };
3980
+ }
3981
+ }
3982
+ };
3983
+ const populateEvent = async (key, item) => {
3984
+ logger23.default("populate event key", { key });
3985
+ const cacheConfig = toCacheConfig(events[key]);
3986
+ if (item.events === void 0) {
3987
+ throw new Error("Item does not have events " + JSON.stringify(item));
3988
+ } else if (item.events[key] === void 0) {
3989
+ if (cacheConfig.optional === false) {
3990
+ logger23.error("Item does not have mandatory event with key " + key + " " + JSON.stringify(item));
3991
+ throw new Error("Item does not have mandatory event with key " + key + " " + JSON.stringify(item));
3992
+ }
3993
+ } else {
3994
+ const event = item.events[key];
3995
+ if (event.by === void 0) {
3996
+ logger23.error(
3997
+ "populateEvent with an Event that does not have by",
3998
+ { event, ik: item.key, eventKey: key }
3999
+ );
4000
+ throw new Error("populateEvent with an Event that does not have by: " + JSON.stringify({ key, event }));
4001
+ }
4002
+ logger23.default("EVENT Retrieving Item in Populate", { key: event.by });
4003
+ const [, newItem] = await cacheConfig.cache.operations.retrieve(event.by);
4004
+ if (newItem) {
4005
+ event.agg = newItem;
4006
+ }
4007
+ }
4008
+ };
4009
+ const all2 = async (query = {}, locations = []) => {
4010
+ logger23.default("all", { query, locations });
4011
+ const [cacheMap, items] = await cache.operations.all(query, locations);
4012
+ const populatedItems = await Promise.all(items.map(async (item) => populate(item)));
4013
+ return [cacheMap, populatedItems];
4014
+ };
4015
+ const one2 = async (query = {}, locations = []) => {
4016
+ logger23.default("one", { query, locations });
4017
+ const [cacheMap, item] = await cache.operations.one(query, locations);
4018
+ let populatedItem = null;
4019
+ if (item) {
4020
+ populatedItem = await populate(item);
4021
+ }
4022
+ return [cacheMap, populatedItem];
4023
+ };
4024
+ const action2 = async (key, action3, body = {}) => {
4025
+ logger23.default("action", { key, action: action3, body });
4026
+ const [cacheMap, item] = await cache.operations.action(key, action3, body);
4027
+ const populatedItem = await populate(item);
4028
+ return [cacheMap, populatedItem];
4029
+ };
4030
+ const allAction2 = async (action3, body = {}, locations = []) => {
4031
+ logger23.default("action", { action: action3, body, locations });
4032
+ const [cacheMap, items] = await cache.operations.allAction(action3, body, locations);
4033
+ const populatedItems = await Promise.all(items.map(async (item) => populate(item)));
4034
+ return [cacheMap, populatedItems];
4035
+ };
4036
+ const allFacet2 = async (facet3, params = {}, locations = []) => {
4037
+ logger23.default("allFacet", { facet: facet3, params, locations });
4038
+ const [cacheMap, response] = await cache.operations.allFacet(facet3, params, locations);
4039
+ return [cacheMap, response];
4040
+ };
4041
+ const create2 = async (v, locations = []) => {
4042
+ logger23.default("create", { v, locations });
4043
+ const [cacheMap, item] = await cache.operations.create(v, locations);
4044
+ const populatedItem = await populate(item);
4045
+ return [cacheMap, populatedItem];
4046
+ };
4047
+ const get2 = async (key) => {
4048
+ logger23.default("get", { key });
4049
+ const [cacheMap, item] = await cache.operations.get(key);
4050
+ let populatedItem = null;
4051
+ if (item) {
4052
+ populatedItem = await populate(item);
4053
+ }
4054
+ return [cacheMap, populatedItem];
4055
+ };
4056
+ const retrieve2 = async (key) => {
4057
+ logger23.default("retrieve", { key });
4058
+ const [cacheMap, item] = await cache.operations.retrieve(key);
4059
+ let populatedItem = null;
4060
+ if (item) {
4061
+ populatedItem = await populate(item);
4062
+ }
4063
+ return [cacheMap, populatedItem];
4064
+ };
4065
+ const remove2 = async (key) => {
4066
+ logger23.default("remove", { key });
4067
+ const cacheMap = await cache.operations.remove(key);
4068
+ return cacheMap;
4069
+ };
4070
+ const update2 = async (key, v) => {
4071
+ logger23.default("update", { key, v });
4072
+ const [cacheMap, item] = await cache.operations.update(key, v);
4073
+ const populatedItem = await populate(item);
4074
+ return [cacheMap, populatedItem];
4075
+ };
4076
+ const facet2 = async (key, facet3) => {
4077
+ logger23.default("facet", { key, facet: facet3 });
4078
+ const [cacheMap, response] = await cache.operations.facet(key, facet3);
4079
+ return [cacheMap, response];
4080
+ };
4081
+ const find2 = async (finder, finderParams = {}, locations = []) => {
4082
+ logger23.default("find", { finder, finderParams, locations });
4083
+ const [cacheMap, items] = await cache.operations.find(finder, finderParams, locations);
4084
+ const populatedItems = await Promise.all(items.map(async (item) => populate(item)));
4085
+ return [cacheMap, populatedItems];
4086
+ };
4087
+ const findOne2 = async (finder, finderParams = {}, locations = []) => {
4088
+ logger23.default("find", { finder, finderParams, locations });
4089
+ const [cacheMap, item] = await cache.operations.findOne(finder, finderParams, locations);
4090
+ const populatedItem = await populate(item);
4091
+ return [cacheMap, populatedItem];
4092
+ };
4093
+ const set2 = async (key, v) => {
4094
+ logger23.default("set", { key, v });
4095
+ const [cacheMap, item] = await cache.operations.set(key, v);
4096
+ const populatedItem = await populate(item);
4097
+ return [cacheMap, populatedItem];
4098
+ };
4099
+ const reset2 = async () => {
4100
+ const cacheMap = await cache.operations.reset();
4101
+ return cacheMap;
4102
+ };
4103
+ return {
4104
+ // Cache properties
4105
+ coordinate: cache.coordinate,
4106
+ registry: cache.registry,
4107
+ api: cache.api,
4108
+ cacheMap: cache.cacheMap,
4109
+ operations: cache.operations,
4110
+ // Cache operations exposed directly
4111
+ all: all2,
4112
+ one: one2,
4113
+ action: action2,
4114
+ allAction: allAction2,
4115
+ allFacet: allFacet2,
4116
+ create: create2,
4117
+ get: get2,
4118
+ retrieve: retrieve2,
4119
+ remove: remove2,
4120
+ update: update2,
4121
+ facet: facet2,
4122
+ find: find2,
4123
+ findOne: findOne2,
4124
+ reset: reset2,
4125
+ set: set2,
4126
+ // Aggregator-specific operations
4127
+ populate,
4128
+ populateAggregate,
4129
+ populateEvent
718
4130
  };
719
4131
  };
720
4132
  export {
4133
+ AsyncIndexDBCacheMap,
721
4134
  CacheMap,
4135
+ EnhancedMemoryCacheMap,
4136
+ IndexDBCacheMap,
4137
+ LocalStorageCacheMap,
4138
+ MemoryCacheMap,
4139
+ SessionStorageCacheMap,
722
4140
  createAggregator,
723
4141
  createCache,
4142
+ createCacheMap,
4143
+ createEvictionStrategy,
724
4144
  createInstance,
725
4145
  createInstanceFactory,
4146
+ createNormalizedHashFunction,
726
4147
  createOperations,
727
- createRegistry,
728
- createRegistryFactory,
4148
+ createOptions,
4149
+ createValidatedConfig,
4150
+ estimateValueSize,
4151
+ formatBytes,
729
4152
  isCache,
730
4153
  isInstance,
731
- toCacheConfig
4154
+ isLocKeyArrayEqual,
4155
+ normalizeKeyValue,
4156
+ normalizeLocKeyItem,
4157
+ parseSizeString,
4158
+ toCacheConfig,
4159
+ validateARCConfig,
4160
+ validateEvictionStrategyConfig,
4161
+ validateLFUConfig,
4162
+ validateOptions,
4163
+ validateSizeConfig,
4164
+ validateTwoQueueConfig
732
4165
  };
733
4166
  //# sourceMappingURL=index.js.map