@warlock.js/cache 5.19.1 → 5.20.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 (102) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/cjs/index.cjs +1439 -331
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/cache-manager.d.mts +4 -0
  5. package/esm/cache-manager.d.mts.map +1 -1
  6. package/esm/cache-manager.mjs +23 -5
  7. package/esm/cache-manager.mjs.map +1 -1
  8. package/esm/cached/auto-key.d.mts +14 -14
  9. package/esm/cached/auto-key.d.mts.map +1 -1
  10. package/esm/cached/auto-key.mjs +50 -34
  11. package/esm/cached/auto-key.mjs.map +1 -1
  12. package/esm/cached/cached.mjs +4 -0
  13. package/esm/cached/cached.mjs.map +1 -1
  14. package/esm/drivers/base-cache-driver.d.mts +84 -8
  15. package/esm/drivers/base-cache-driver.d.mts.map +1 -1
  16. package/esm/drivers/base-cache-driver.mjs +152 -42
  17. package/esm/drivers/base-cache-driver.mjs.map +1 -1
  18. package/esm/drivers/file-cache-driver.d.mts +15 -0
  19. package/esm/drivers/file-cache-driver.d.mts.map +1 -1
  20. package/esm/drivers/file-cache-driver.mjs +68 -18
  21. package/esm/drivers/file-cache-driver.mjs.map +1 -1
  22. package/esm/drivers/in-memory-tag-index.d.mts +44 -0
  23. package/esm/drivers/in-memory-tag-index.d.mts.map +1 -0
  24. package/esm/drivers/in-memory-tag-index.mjs +73 -0
  25. package/esm/drivers/in-memory-tag-index.mjs.map +1 -0
  26. package/esm/drivers/index.d.mts +1 -1
  27. package/esm/drivers/index.mjs +1 -1
  28. package/esm/drivers/lru-memory-cache-driver.d.mts +43 -0
  29. package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
  30. package/esm/drivers/lru-memory-cache-driver.mjs +96 -0
  31. package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
  32. package/esm/drivers/memory-cache-driver.d.mts +87 -30
  33. package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
  34. package/esm/drivers/memory-cache-driver.mjs +214 -96
  35. package/esm/drivers/memory-cache-driver.mjs.map +1 -1
  36. package/esm/drivers/memory-extended-cache-driver.d.mts +9 -5
  37. package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
  38. package/esm/drivers/memory-extended-cache-driver.mjs +10 -23
  39. package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
  40. package/esm/drivers/mock-cache-driver.d.mts +41 -0
  41. package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
  42. package/esm/drivers/mock-cache-driver.mjs +105 -8
  43. package/esm/drivers/mock-cache-driver.mjs.map +1 -1
  44. package/esm/drivers/null-cache-driver.d.mts +8 -2
  45. package/esm/drivers/null-cache-driver.d.mts.map +1 -1
  46. package/esm/drivers/null-cache-driver.mjs +10 -2
  47. package/esm/drivers/null-cache-driver.mjs.map +1 -1
  48. package/esm/drivers/pg-cache-driver.d.mts +77 -3
  49. package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
  50. package/esm/drivers/pg-cache-driver.mjs +229 -7
  51. package/esm/drivers/pg-cache-driver.mjs.map +1 -1
  52. package/esm/drivers/redis-cache-driver.d.mts +86 -1
  53. package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
  54. package/esm/drivers/redis-cache-driver.mjs +252 -16
  55. package/esm/drivers/redis-cache-driver.mjs.map +1 -1
  56. package/esm/index.d.mts +2 -2
  57. package/esm/index.mjs +2 -2
  58. package/esm/list/memory-cache-list.d.mts +11 -8
  59. package/esm/list/memory-cache-list.d.mts.map +1 -1
  60. package/esm/list/memory-cache-list.mjs +39 -37
  61. package/esm/list/memory-cache-list.mjs.map +1 -1
  62. package/esm/scoped-cache.d.mts +9 -3
  63. package/esm/scoped-cache.d.mts.map +1 -1
  64. package/esm/scoped-cache.mjs +19 -7
  65. package/esm/scoped-cache.mjs.map +1 -1
  66. package/esm/tagged-cache.d.mts +20 -0
  67. package/esm/tagged-cache.d.mts.map +1 -1
  68. package/esm/tagged-cache.mjs +62 -24
  69. package/esm/tagged-cache.mjs.map +1 -1
  70. package/esm/tagged-scoped-cache.d.mts +7 -3
  71. package/esm/tagged-scoped-cache.d.mts.map +1 -1
  72. package/esm/tagged-scoped-cache.mjs +19 -7
  73. package/esm/tagged-scoped-cache.mjs.map +1 -1
  74. package/esm/types.d.mts +41 -3
  75. package/esm/types.d.mts.map +1 -1
  76. package/esm/types.mjs.map +1 -1
  77. package/esm/utils.d.mts +9 -3
  78. package/esm/utils.d.mts.map +1 -1
  79. package/esm/utils.mjs +16 -6
  80. package/esm/utils.mjs.map +1 -1
  81. package/llms-full.txt +1685 -1666
  82. package/llms.txt +18 -18
  83. package/package.json +4 -4
  84. package/skills/apply-cache-patterns/SKILL.md +98 -97
  85. package/skills/configure-pg-cache/SKILL.md +6 -5
  86. package/skills/configure-set-options/SKILL.md +102 -96
  87. package/skills/handle-cache-errors/SKILL.md +92 -92
  88. package/skills/index.md +20 -0
  89. package/skills/observe-cache/SKILL.md +103 -103
  90. package/skills/pick-cache-driver/SKILL.md +129 -123
  91. package/skills/test-cache-code/SKILL.md +219 -219
  92. package/skills/use-cache-atomic/SKILL.md +57 -65
  93. package/skills/use-cache-bulk/SKILL.md +57 -57
  94. package/skills/use-cache-list/SKILL.md +85 -85
  95. package/skills/use-cache-lock/SKILL.md +113 -104
  96. package/skills/use-cache-namespace/SKILL.md +88 -88
  97. package/skills/use-cache-similarity/SKILL.md +94 -94
  98. package/skills/use-cache-tags/SKILL.md +7 -4
  99. package/skills/use-cache-update-merge/SKILL.md +84 -84
  100. package/skills/use-cache-utils/SKILL.md +89 -89
  101. package/skills/use-cached-hof/SKILL.md +100 -99
  102. package/skills/use-swr/SKILL.md +104 -104
@@ -1,4 +1,5 @@
1
1
  import { BaseCacheDriver } from "./base-cache-driver.mjs";
2
+ import { InMemoryTagIndex } from "./in-memory-tag-index.mjs";
2
3
  import { CacheCall, CacheData, CacheDriver, CacheKey, CacheSetOptions, CacheTtl, MockCacheOptions } from "../types.mjs";
3
4
 
4
5
  //#region ../cache/src/drivers/mock-cache-driver.d.ts
@@ -57,6 +58,10 @@ declare class MockCacheDriver extends BaseCacheDriver<MockCacheDriver, MockCache
57
58
  * so tests can introspect raw entries when {@link getStored} isn't enough.
58
59
  */
59
60
  readonly storage: Map<string, CacheData>;
61
+ /**
62
+ * Tag index, kept apart from {@link storage} (same model as the memory driver).
63
+ */
64
+ protected tagIndex: InMemoryTagIndex;
60
65
  /**
61
66
  * Ordered record of every public operation routed through this driver.
62
67
  * Pushed to before each op runs; tests assert via {@link wasCalled} or by
@@ -82,11 +87,29 @@ declare class MockCacheDriver extends BaseCacheDriver<MockCacheDriver, MockCache
82
87
  * via {@link BaseCacheDriver.resolveSetOptions}.
83
88
  */
84
89
  set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;
90
+ /**
91
+ * Clone non-primitive values so stored state can't be mutated by callers.
92
+ */
93
+ protected cloneValue<T>(value: T): T;
85
94
  /**
86
95
  * Standard `get` with TTL handling. Emits `hit` / `miss` events to keep
87
96
  * downstream metrics tests realistic.
88
97
  */
89
98
  get<T = any>(key: CacheKey): Promise<T | null>;
99
+ /**
100
+ * Live entry for `parsedKey`, or `undefined` when missing / expired (an
101
+ * expired entry is deleted). Synchronous.
102
+ */
103
+ protected liveEntry(parsedKey: string): CacheData | undefined;
104
+ /**
105
+ * Synchronous read-add-write (atomic within the process). Keeps an existing
106
+ * entry's expiry; a missing key starts from 0 with the default TTL.
107
+ */
108
+ increment(key: CacheKey, value?: number): Promise<number>;
109
+ /**
110
+ * Synchronous read-and-delete: a value is handed out exactly once.
111
+ */
112
+ pull<T = any>(key: CacheKey): Promise<T | null>;
90
113
  /**
91
114
  * Read the raw {@link CacheData} wrapper from the in-memory `Map`,
92
115
  * including `staleAt` metadata. Returns `null` for missing or expired
@@ -97,6 +120,24 @@ declare class MockCacheDriver extends BaseCacheDriver<MockCacheDriver, MockCache
97
120
  * Standard `remove` — drops the entry and emits the `removed` event.
98
121
  */
99
122
  remove(key: CacheKey): Promise<void>;
123
+ /**
124
+ * {@inheritdoc}
125
+ *
126
+ * Synchronous in-process set, never evicted or expired with the entries.
127
+ */
128
+ tagAdd(tagKey: CacheKey, members: string[]): Promise<void>;
129
+ /**
130
+ * {@inheritdoc}
131
+ */
132
+ tagMembers(tagKey: CacheKey): Promise<string[]>;
133
+ /**
134
+ * {@inheritdoc}
135
+ */
136
+ tagRemove(tagKey: CacheKey, members: string[]): Promise<void>;
137
+ /**
138
+ * {@inheritdoc}
139
+ */
140
+ tagDelete(tagKey: CacheKey): Promise<void>;
100
141
  /**
101
142
  * Standard `flush` — wipes the entire mock store + tag index. Does NOT
102
143
  * touch the call log; use {@link reset} to clear that as well.
@@ -1 +1 @@
1
- {"version":3,"file":"mock-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/mock-cache-driver.ts"],"mappings":";;;;;;AAqDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,eAAA,SACH,eAAA,CAAgB,eAAA,EAAiB,gBAAA,aAC9B,WAAA,CAAY,eAAA,EAAiB,gBAAA;EAKjC;;;EAAA,IAAA;EAWkB;;;EANlB,OAAA,EAAS,gBAAA;EAmBH;;;;EAAA,SAbG,OAAA,EAAS,GAAA,SAAY,SAAA;EA8BR;;;;;EAAA,SAvBb,OAAA,EAAS,SAAA;EA+CR;;;;EAzCJ,OAAA,IAAW,OAAA;EAuFP;;;EA/EJ,UAAA,IAAc,OAAA;EA+EuB;;;;EAtErC,eAAA,CAAgB,SAAA,WAAoB,OAAA;EAyGA;;;;EApFpC,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAkHU;;;;EArEA,GAAA,UAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAyFd;;;;;EAAA,UAtDpB,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAwF1C;;;EAtEM,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAkFlC;;AAAoB;;EAlET,KAAA,IAAS,OAAA;;;;;;;;;;;EAoBf,SAAA,CAAU,SAAA,UAAmB,GAAA,GAAM,QAAA;;;;;;;;EAmBnC,SAAA,UAAmB,GAAA,EAAK,QAAA,GAAW,CAAA;;;;;EAenC,KAAA;;;;;YASG,UAAA,CACR,SAAA,UACA,GAAA,sBACA,IAAA;AAAA"}
1
+ {"version":3,"file":"mock-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/mock-cache-driver.ts"],"mappings":";;;;;;;AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,eAAA,SACH,eAAA,CAAgB,eAAA,EAAiB,gBAAA,aAC9B,WAAA,CAAY,eAAA,EAAiB,gBAAA;EA6SqB;;;EAxStD,IAAA;EA4UmC;;;EAvUnC,OAAA,EAAS,gBAAA;EAVL;;;;EAAA,SAgBK,OAAA,EAAS,GAAA,SAAY,SAAA;EAjBI;;;EAAA,UAsB/B,QAAA,EAAU,gBAAA;EAhBb;;;;;EAAA,SAuBS,OAAA,EAAS,SAAA;EAPf;;;;EAaG,OAAA,IAAW,OAAA;EAAA;;;EAQX,UAAA,IAAc,OAAA;EASE;;;;EAAhB,eAAA,CAAgB,SAAA,WAAoB,OAAA;EAyB/C;;;;EAFW,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAoDO;;;EAAA,UAAA,UAAA,IAAc,KAAA,EAAO,CAAA,GAAI,CAAA;EAAA;;;;EAYtB,GAAA,UAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAAR;;;;EAAA,UAkChC,SAAA,CAAU,SAAA,WAAoB,SAAA;EAoB3B;;;;EAAA,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EA4B7C;;;EAAA,IAAA,UAAc,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAAR;;;;;EAAA,UA4B3B,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAAA;;;EAkBpC,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAAA;;;;;EAiBvB,MAAA,CAAO,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAO7C;;;EAAA,UAAA,CAAW,MAAA,EAAQ,QAAA,GAAW,OAAA;EAO9B;;;EAAA,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAAA;;;EAOhD,SAAA,CAAU,MAAA,EAAQ,QAAA,GAAW,OAAA;EAAA;;;;EAQ7B,KAAA,IAAS,OAAA;EAqBoB;;;;;;;;;;EAAnC,SAAA,CAAU,SAAA,UAAmB,GAAA,GAAM,QAAA;EA+CxC;;AAAoB;;;;;EA5Bf,SAAA,UAAmB,GAAA,EAAK,QAAA,GAAW,CAAA;;;;;EAenC,KAAA;;;;;YAUG,UAAA,CACR,SAAA,UACA,GAAA,sBACA,IAAA;AAAA"}
@@ -1,4 +1,5 @@
1
1
  import { BaseCacheDriver } from "./base-cache-driver.mjs";
2
+ import { InMemoryTagIndex } from "./in-memory-tag-index.mjs";
2
3
 
3
4
  //#region ../cache/src/drivers/mock-cache-driver.ts
4
5
  /**
@@ -48,6 +49,7 @@ var MockCacheDriver = class extends BaseCacheDriver {
48
49
  this.name = "mock";
49
50
  this.options = {};
50
51
  this.storage = /* @__PURE__ */ new Map();
52
+ this.tagIndex = new InMemoryTagIndex();
51
53
  this.callLog = [];
52
54
  }
53
55
  /**
@@ -74,6 +76,7 @@ var MockCacheDriver = class extends BaseCacheDriver {
74
76
  this.recordCall("removeNamespace", parsed, [namespace]);
75
77
  this.log("clearing", parsed);
76
78
  const prefix = parsed + ".";
79
+ this.tagIndex.removeNamespace(parsed);
77
80
  for (const key of [...this.storage.keys()]) if (key === parsed || key.startsWith(prefix)) this.storage.delete(key);
78
81
  this.log("cleared", parsed);
79
82
  }
@@ -86,17 +89,20 @@ var MockCacheDriver = class extends BaseCacheDriver {
86
89
  const { ttl, tags, onConflict, staleAt } = this.resolveSetOptions(ttlOrOptions);
87
90
  this.recordCall("set", parsedKey, [value, ttlOrOptions]);
88
91
  this.log("caching", parsedKey);
89
- const existing = onConflict === "upsert" ? null : await this.get(key);
90
- const exists = existing !== null;
91
- if (onConflict === "create" && exists) return {
92
+ let existing = onConflict === "create" || onConflict === "update" ? this.storage.get(parsedKey) : void 0;
93
+ if (existing && existing.expiresAt !== void 0 && existing.expiresAt <= Date.now()) {
94
+ this.storage.delete(parsedKey);
95
+ existing = void 0;
96
+ }
97
+ if (onConflict === "create" && existing) return {
92
98
  wasSet: false,
93
- existing
99
+ existing: this.cloneValue(existing.data)
94
100
  };
95
- if (onConflict === "update" && !exists) return {
101
+ if (onConflict === "update" && !existing) return {
96
102
  wasSet: false,
97
103
  existing: null
98
104
  };
99
- const data = this.prepareDataForStorage(value, ttl, staleAt);
105
+ const data = this.prepareDataForStorage(this.cloneValue(value), ttl, staleAt);
100
106
  this.storage.set(parsedKey, data);
101
107
  if (tags && tags.length > 0) await this.applyTags(key, tags);
102
108
  this.log("cached", parsedKey);
@@ -109,7 +115,14 @@ var MockCacheDriver = class extends BaseCacheDriver {
109
115
  wasSet: true,
110
116
  existing: null
111
117
  };
112
- return value;
118
+ return this;
119
+ }
120
+ /**
121
+ * Clone non-primitive values so stored state can't be mutated by callers.
122
+ */
123
+ cloneValue(value) {
124
+ if (value === null || value === void 0 || typeof value !== "object") return value;
125
+ return structuredClone(value);
113
126
  }
114
127
  /**
115
128
  * Standard `get` with TTL handling. Emits `hit` / `miss` events to keep
@@ -138,6 +151,59 @@ var MockCacheDriver = class extends BaseCacheDriver {
138
151
  return value;
139
152
  }
140
153
  /**
154
+ * Live entry for `parsedKey`, or `undefined` when missing / expired (an
155
+ * expired entry is deleted). Synchronous.
156
+ */
157
+ liveEntry(parsedKey) {
158
+ const entry = this.storage.get(parsedKey);
159
+ if (!entry) return;
160
+ if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) {
161
+ this.storage.delete(parsedKey);
162
+ return;
163
+ }
164
+ return entry;
165
+ }
166
+ /**
167
+ * Synchronous read-add-write (atomic within the process). Keeps an existing
168
+ * entry's expiry; a missing key starts from 0 with the default TTL.
169
+ */
170
+ async increment(key, value = 1) {
171
+ const parsedKey = this.parseKey(key);
172
+ this.recordCall("increment", parsedKey, [value]);
173
+ const entry = this.liveEntry(parsedKey);
174
+ const current = entry ? entry.data : 0;
175
+ if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${parsedKey}`);
176
+ const newValue = current + value;
177
+ if (entry) entry.data = newValue;
178
+ else this.storage.set(parsedKey, this.prepareDataForStorage(newValue, this.ttl));
179
+ await this.emit("set", {
180
+ key: parsedKey,
181
+ value: newValue,
182
+ ttl: this.ttl
183
+ });
184
+ return newValue;
185
+ }
186
+ /**
187
+ * Synchronous read-and-delete: a value is handed out exactly once.
188
+ */
189
+ async pull(key) {
190
+ const parsedKey = this.parseKey(key);
191
+ this.recordCall("pull", parsedKey);
192
+ const entry = this.liveEntry(parsedKey);
193
+ if (!entry) {
194
+ await this.emit("miss", { key: parsedKey });
195
+ return null;
196
+ }
197
+ this.storage.delete(parsedKey);
198
+ const value = this.cloneValue(entry.data);
199
+ await this.emit("hit", {
200
+ key: parsedKey,
201
+ value
202
+ });
203
+ await this.emit("removed", { key: parsedKey });
204
+ return value;
205
+ }
206
+ /**
141
207
  * Read the raw {@link CacheData} wrapper from the in-memory `Map`,
142
208
  * including `staleAt` metadata. Returns `null` for missing or expired
143
209
  * entries — `swr()` consumes this to branch on freshness.
@@ -147,7 +213,10 @@ var MockCacheDriver = class extends BaseCacheDriver {
147
213
  const entry = this.storage.get(parsedKey);
148
214
  if (!entry) return null;
149
215
  if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) return null;
150
- return entry;
216
+ return {
217
+ ...entry,
218
+ data: this.cloneValue(entry.data)
219
+ };
151
220
  }
152
221
  /**
153
222
  * Standard `remove` — drops the entry and emits the `removed` event.
@@ -161,6 +230,32 @@ var MockCacheDriver = class extends BaseCacheDriver {
161
230
  await this.emit("removed", { key: parsedKey });
162
231
  }
163
232
  /**
233
+ * {@inheritdoc}
234
+ *
235
+ * Synchronous in-process set, never evicted or expired with the entries.
236
+ */
237
+ async tagAdd(tagKey, members) {
238
+ this.tagIndex.add(this.parseKey(tagKey), members);
239
+ }
240
+ /**
241
+ * {@inheritdoc}
242
+ */
243
+ async tagMembers(tagKey) {
244
+ return this.tagIndex.members(this.parseKey(tagKey));
245
+ }
246
+ /**
247
+ * {@inheritdoc}
248
+ */
249
+ async tagRemove(tagKey, members) {
250
+ this.tagIndex.remove(this.parseKey(tagKey), members);
251
+ }
252
+ /**
253
+ * {@inheritdoc}
254
+ */
255
+ async tagDelete(tagKey) {
256
+ this.tagIndex.delete(this.parseKey(tagKey));
257
+ }
258
+ /**
164
259
  * Standard `flush` — wipes the entire mock store + tag index. Does NOT
165
260
  * touch the call log; use {@link reset} to clear that as well.
166
261
  */
@@ -168,6 +263,7 @@ var MockCacheDriver = class extends BaseCacheDriver {
168
263
  this.recordCall("flush", void 0);
169
264
  this.log("flushing");
170
265
  this.storage.clear();
266
+ this.tagIndex.clear();
171
267
  this.log("flushed");
172
268
  await this.emit("flushed");
173
269
  }
@@ -205,6 +301,7 @@ var MockCacheDriver = class extends BaseCacheDriver {
205
301
  */
206
302
  reset() {
207
303
  this.storage.clear();
304
+ this.tagIndex.clear();
208
305
  this.callLog.length = 0;
209
306
  }
210
307
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"mock-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/mock-cache-driver.ts"],"sourcesContent":["import type {\n CacheCall,\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n MockCacheOptions,\n} from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\n/**\n * In-memory cache driver with introspection helpers, intended for use as a\n * test double in downstream packages.\n *\n * **Role.** Drop-in replacement for any real driver in test setups, with\n * extra surface that makes behavioral assertions easy: every public op gets\n * recorded into {@link MockCacheDriver.callLog}, and {@link wasCalled} /\n * {@link getStored} / {@link reset} let tests verify side effects without\n * pulling in a real Redis / Postgres / file system.\n *\n * **Responsibility.**\n * - Owns: in-memory storage backed by a `Map`, the `callLog`, TTL handling\n * via `parseCachedData`, `onConflict` policies, and tag-index storage.\n * - Does NOT own: similarity retrieval (vectors are recorded into the\n * `callLog` but `similar()` throws — use `MemoryCacheDriver` for tests\n * that need real nearest-neighbor scoring), connection lifecycle\n * (no-op `connect`/`disconnect`), or eviction (no `maxSize`).\n *\n * Register it like any other driver — sub-paths are not part of the\n * package's export convention; the same single barrel ships it next to\n * the production drivers.\n *\n * @example\n * import { cache, MockCacheDriver } from \"@warlock.js/cache\";\n *\n * beforeEach(async () => {\n * cache.setCacheConfigurations({\n * default: \"mock\",\n * drivers: { mock: MockCacheDriver },\n * options: { mock: {} },\n * });\n * await cache.init();\n * });\n *\n * it(\"invalidates the user cache after update\", async () => {\n * await userService.update(42, { name: \"Jane\" });\n *\n * const driver = cache.currentDriver as MockCacheDriver;\n * expect(driver.wasCalled(\"remove\", \"users.42\")).toBe(true);\n * });\n */\nexport class MockCacheDriver\n extends BaseCacheDriver<MockCacheDriver, MockCacheOptions>\n implements CacheDriver<MockCacheDriver, MockCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"mock\";\n\n /**\n * {@inheritdoc}\n */\n public options: MockCacheOptions = {};\n\n /**\n * Storage backing the mock — keyed by post-`parseKey` string. Public-readonly\n * so tests can introspect raw entries when {@link getStored} isn't enough.\n */\n public readonly storage: Map<string, CacheData> = new Map();\n\n /**\n * Ordered record of every public operation routed through this driver.\n * Pushed to before each op runs; tests assert via {@link wasCalled} or by\n * inspecting the array directly.\n */\n public readonly callLog: CacheCall[] = [];\n\n /**\n * Standard driver setup. Mirrors the null driver — no connection, no\n * resources to release.\n */\n public async connect(): Promise<void> {\n this.recordCall(\"connect\", undefined);\n await super.connect();\n }\n\n /**\n * Standard driver teardown.\n */\n public async disconnect(): Promise<void> {\n this.recordCall(\"disconnect\", undefined);\n await super.disconnect();\n }\n\n /**\n * Wipe everything under `namespace`. Matches `MemoryCacheDriver` semantics\n * — the namespace itself and any key with the namespace prefix is removed.\n */\n public async removeNamespace(namespace: string): Promise<void> {\n const parsed = this.parseKey(namespace);\n\n this.recordCall(\"removeNamespace\", parsed, [namespace]);\n this.log(\"clearing\", parsed);\n\n const prefix = parsed + \".\";\n\n for (const key of [...this.storage.keys()]) {\n if (key === parsed || key.startsWith(prefix)) {\n this.storage.delete(key);\n }\n }\n\n this.log(\"cleared\", parsed);\n }\n\n /**\n * Standard `set` with full `onConflict` support. Honors scope-default TTL\n * via {@link BaseCacheDriver.resolveSetOptions}.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.recordCall(\"set\", parsedKey, [value, ttlOrOptions]);\n this.log(\"caching\", parsedKey);\n\n const existing = onConflict === \"upsert\" ? null : await this.get(key);\n const exists = existing !== null;\n\n if (onConflict === \"create\" && exists) {\n const result: CacheSetResult = { wasSet: false, existing };\n\n return result;\n }\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n\n return result;\n }\n\n const data = this.prepareDataForStorage(value, ttl, staleAt);\n this.storage.set(parsedKey, data);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n\n return result;\n }\n\n return value;\n }\n\n /**\n * Standard `get` with TTL handling. Emits `hit` / `miss` events to keep\n * downstream metrics tests realistic.\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"get\", parsedKey);\n this.log(\"fetching\", parsedKey);\n\n const data = this.storage.get(parsedKey);\n\n if (!data) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n const value = await this.parseCachedData(parsedKey, data);\n\n if (value === null) {\n // expired — the parse helper already logged + queued the cleanup\n this.storage.delete(parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n await this.emit(\"hit\", { key: parsedKey, value });\n\n return value as T;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from the in-memory `Map`,\n * including `staleAt` metadata. Returns `null` for missing or expired\n * entries — `swr()` consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return entry;\n }\n\n /**\n * Standard `remove` — drops the entry and emits the `removed` event.\n */\n public async remove(key: CacheKey): Promise<void> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"remove\", parsedKey);\n this.log(\"removing\", parsedKey);\n\n this.storage.delete(parsedKey);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * Standard `flush` — wipes the entire mock store + tag index. Does NOT\n * touch the call log; use {@link reset} to clear that as well.\n */\n public async flush(): Promise<void> {\n this.recordCall(\"flush\", undefined);\n this.log(\"flushing\");\n\n this.storage.clear();\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * Was a given operation invoked? When `key` is provided, the match is\n * post-`parseKey` so callers pass the same key shape they used at the\n * call site — strings or objects, both resolve to the same parsed key.\n *\n * @example\n * driver.wasCalled(\"set\"); // any set\n * driver.wasCalled(\"set\", \"users.42\"); // set on this specific key\n * driver.wasCalled(\"set\", { id: 42 }); // same — object key normalized\n */\n public wasCalled(operation: string, key?: CacheKey): boolean {\n if (key === undefined) {\n return this.callLog.some((call) => call.operation === operation);\n }\n\n const parsedKey = this.parseKey(key);\n\n return this.callLog.some(\n (call) => call.operation === operation && call.key === parsedKey,\n );\n }\n\n /**\n * Return the raw stored value for `key`, bypassing TTL handling and clone\n * protection. Useful when a test wants to assert on the persisted shape\n * (or assert that an entry expired without going through `get`).\n *\n * Returns `undefined` when the key isn't present.\n */\n public getStored<T = any>(key: CacheKey): T | undefined {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n return entry.data as T;\n }\n\n /**\n * Wipe everything — storage, tag index, and the call log. Pair with\n * Vitest's `beforeEach` to get clean isolation between tests.\n */\n public reset(): void {\n this.storage.clear();\n this.callLog.length = 0;\n }\n\n /**\n * Append a row to {@link callLog}. Internal helper called by every\n * recorded op before the actual work runs.\n */\n protected recordCall(\n operation: string,\n key: string | undefined,\n args: unknown[] = [],\n ): void {\n this.callLog.push({\n operation,\n key,\n args,\n timestamp: Date.now(),\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mock-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/mock-cache-driver.ts"],"sourcesContent":["import type {\n CacheCall,\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n MockCacheOptions,\n} from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * In-memory cache driver with introspection helpers, intended for use as a\n * test double in downstream packages.\n *\n * **Role.** Drop-in replacement for any real driver in test setups, with\n * extra surface that makes behavioral assertions easy: every public op gets\n * recorded into {@link MockCacheDriver.callLog}, and {@link wasCalled} /\n * {@link getStored} / {@link reset} let tests verify side effects without\n * pulling in a real Redis / Postgres / file system.\n *\n * **Responsibility.**\n * - Owns: in-memory storage backed by a `Map`, the `callLog`, TTL handling\n * via `parseCachedData`, `onConflict` policies, and tag-index storage.\n * - Does NOT own: similarity retrieval (vectors are recorded into the\n * `callLog` but `similar()` throws — use `MemoryCacheDriver` for tests\n * that need real nearest-neighbor scoring), connection lifecycle\n * (no-op `connect`/`disconnect`), or eviction (no `maxSize`).\n *\n * Register it like any other driver — sub-paths are not part of the\n * package's export convention; the same single barrel ships it next to\n * the production drivers.\n *\n * @example\n * import { cache, MockCacheDriver } from \"@warlock.js/cache\";\n *\n * beforeEach(async () => {\n * cache.setCacheConfigurations({\n * default: \"mock\",\n * drivers: { mock: MockCacheDriver },\n * options: { mock: {} },\n * });\n * await cache.init();\n * });\n *\n * it(\"invalidates the user cache after update\", async () => {\n * await userService.update(42, { name: \"Jane\" });\n *\n * const driver = cache.currentDriver as MockCacheDriver;\n * expect(driver.wasCalled(\"remove\", \"users.42\")).toBe(true);\n * });\n */\nexport class MockCacheDriver\n extends BaseCacheDriver<MockCacheDriver, MockCacheOptions>\n implements CacheDriver<MockCacheDriver, MockCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"mock\";\n\n /**\n * {@inheritdoc}\n */\n public options: MockCacheOptions = {};\n\n /**\n * Storage backing the mock — keyed by post-`parseKey` string. Public-readonly\n * so tests can introspect raw entries when {@link getStored} isn't enough.\n */\n public readonly storage: Map<string, CacheData> = new Map();\n\n /**\n * Tag index, kept apart from {@link storage} (same model as the memory driver).\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Ordered record of every public operation routed through this driver.\n * Pushed to before each op runs; tests assert via {@link wasCalled} or by\n * inspecting the array directly.\n */\n public readonly callLog: CacheCall[] = [];\n\n /**\n * Standard driver setup. Mirrors the null driver — no connection, no\n * resources to release.\n */\n public async connect(): Promise<void> {\n this.recordCall(\"connect\", undefined);\n await super.connect();\n }\n\n /**\n * Standard driver teardown.\n */\n public async disconnect(): Promise<void> {\n this.recordCall(\"disconnect\", undefined);\n await super.disconnect();\n }\n\n /**\n * Wipe everything under `namespace`. Matches `MemoryCacheDriver` semantics\n * — the namespace itself and any key with the namespace prefix is removed.\n */\n public async removeNamespace(namespace: string): Promise<void> {\n const parsed = this.parseKey(namespace);\n\n this.recordCall(\"removeNamespace\", parsed, [namespace]);\n this.log(\"clearing\", parsed);\n\n const prefix = parsed + \".\";\n\n this.tagIndex.removeNamespace(parsed);\n\n for (const key of [...this.storage.keys()]) {\n if (key === parsed || key.startsWith(prefix)) {\n this.storage.delete(key);\n }\n }\n\n this.log(\"cleared\", parsed);\n }\n\n /**\n * Standard `set` with full `onConflict` support. Honors scope-default TTL\n * via {@link BaseCacheDriver.resolveSetOptions}.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.recordCall(\"set\", parsedKey, [value, ttlOrOptions]);\n this.log(\"caching\", parsedKey);\n\n // Synchronous check + write (no await between) so onConflict is atomic.\n // Expired entries count as missing and are deleted.\n let existing = onConflict === \"create\" || onConflict === \"update\"\n ? this.storage.get(parsedKey)\n : undefined;\n\n if (existing && existing.expiresAt !== undefined && existing.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n existing = undefined;\n }\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: this.cloneValue(existing.data) };\n\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n\n return result;\n }\n\n const data = this.prepareDataForStorage(this.cloneValue(value), ttl, staleAt);\n this.storage.set(parsedKey, data);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n\n return result;\n }\n\n return this;\n }\n\n /**\n * Clone non-primitive values so stored state can't be mutated by callers.\n */\n protected cloneValue<T>(value: T): T {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return value;\n }\n\n return structuredClone(value);\n }\n\n /**\n * Standard `get` with TTL handling. Emits `hit` / `miss` events to keep\n * downstream metrics tests realistic.\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"get\", parsedKey);\n this.log(\"fetching\", parsedKey);\n\n const data = this.storage.get(parsedKey);\n\n if (!data) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n const value = await this.parseCachedData(parsedKey, data);\n\n if (value === null) {\n // expired — the parse helper already logged + queued the cleanup\n this.storage.delete(parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n await this.emit(\"hit\", { key: parsedKey, value });\n\n return value as T;\n }\n\n /**\n * Live entry for `parsedKey`, or `undefined` when missing / expired (an\n * expired entry is deleted). Synchronous.\n */\n protected liveEntry(parsedKey: string): CacheData | undefined {\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Synchronous read-add-write (atomic within the process). Keeps an existing\n * entry's expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"increment\", parsedKey, [value]);\n\n const entry = this.liveEntry(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${parsedKey}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n } else {\n this.storage.set(parsedKey, this.prepareDataForStorage(newValue, this.ttl));\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: this.ttl });\n\n return newValue;\n }\n\n /**\n * Synchronous read-and-delete: a value is handed out exactly once.\n */\n public async pull<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"pull\", parsedKey);\n\n const entry = this.liveEntry(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.storage.delete(parsedKey);\n\n const value = this.cloneValue(entry.data) as T;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from the in-memory `Map`,\n * including `staleAt` metadata. Returns `null` for missing or expired\n * entries — `swr()` consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return { ...entry, data: this.cloneValue(entry.data) };\n }\n\n /**\n * Standard `remove` — drops the entry and emits the `removed` event.\n */\n public async remove(key: CacheKey): Promise<void> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"remove\", parsedKey);\n this.log(\"removing\", parsedKey);\n\n this.storage.delete(parsedKey);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * Standard `flush` — wipes the entire mock store + tag index. Does NOT\n * touch the call log; use {@link reset} to clear that as well.\n */\n public async flush(): Promise<void> {\n this.recordCall(\"flush\", undefined);\n this.log(\"flushing\");\n\n this.storage.clear();\n this.tagIndex.clear();\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * Was a given operation invoked? When `key` is provided, the match is\n * post-`parseKey` so callers pass the same key shape they used at the\n * call site — strings or objects, both resolve to the same parsed key.\n *\n * @example\n * driver.wasCalled(\"set\"); // any set\n * driver.wasCalled(\"set\", \"users.42\"); // set on this specific key\n * driver.wasCalled(\"set\", { id: 42 }); // same — object key normalized\n */\n public wasCalled(operation: string, key?: CacheKey): boolean {\n if (key === undefined) {\n return this.callLog.some((call) => call.operation === operation);\n }\n\n const parsedKey = this.parseKey(key);\n\n return this.callLog.some(\n (call) => call.operation === operation && call.key === parsedKey,\n );\n }\n\n /**\n * Return the raw stored value for `key`, bypassing TTL handling and clone\n * protection. Useful when a test wants to assert on the persisted shape\n * (or assert that an entry expired without going through `get`).\n *\n * Returns `undefined` when the key isn't present.\n */\n public getStored<T = any>(key: CacheKey): T | undefined {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n return entry.data as T;\n }\n\n /**\n * Wipe everything — storage, tag index, and the call log. Pair with\n * Vitest's `beforeEach` to get clean isolation between tests.\n */\n public reset(): void {\n this.storage.clear();\n this.tagIndex.clear();\n this.callLog.length = 0;\n }\n\n /**\n * Append a row to {@link callLog}. Internal helper called by every\n * recorded op before the actual work runs.\n */\n protected recordCall(\n operation: string,\n key: string | undefined,\n args: unknown[] = [],\n ): void {\n this.callLog.push({\n operation,\n key,\n args,\n timestamp: Date.now(),\n });\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { BaseCacheDriver } from "./base-cache-driver.mjs";
2
- import { CacheDriver, CacheKey, CacheSetOptions, CacheTtl, NullCacheDriverOptions } from "../types.mjs";
2
+ import { CacheDriver, CacheKey, CacheSetOptions, CacheTtl, NullCacheDriverOptions, RememberOptions } from "../types.mjs";
3
3
  import { GenericObject } from "@mongez/reinforcements";
4
4
 
5
5
  //#region ../cache/src/drivers/null-cache-driver.d.ts
@@ -31,7 +31,13 @@ declare class NullCacheDriver extends BaseCacheDriver<NullCacheDriver, NullCache
31
31
  /**
32
32
  * {@inheritdoc}
33
33
  */
34
- parseKey(_key: CacheKey): string;
34
+ parseKey(key: CacheKey): string;
35
+ /**
36
+ * {@inheritdoc}
37
+ *
38
+ * Nothing is stored, so always run the callback and return its own result.
39
+ */
40
+ remember(_key: CacheKey, _ttlOrOptions: CacheTtl | RememberOptions, callback: () => Promise<any>): Promise<any>;
35
41
  /**
36
42
  * {@inheritdoc}
37
43
  */
@@ -1 +1 @@
1
- {"version":3,"file":"null-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/null-cache-driver.ts"],"mappings":";;;;;cAWa,eAAA,SACH,eAAA,CAAgB,eAAA,EAAiB,sBAAA,aAC9B,WAAA,CAAY,eAAA,EAAiB,sBAAA;;AAF1C;;EAOS,OAAA,EAAS,aAAA;EANQ;;;EAWjB,IAAA;EALS;;;EAUT,IAAA,EAAM,aAAA;EA4BS;;;EAAA,IAvBX,MAAA;EA4CkB;;;cArCV,OAAA,GAAS,aAAA;EA4DH;;;EApDlB,UAAA,CAAW,OAAA,EAAS,aAAA;EAgFP;;;EAxEb,QAAA,CAAS,IAAA,EAAM,QAAA;EA3CA;;;EAkDT,eAAA,CAAgB,SAAA,WAAiB,OAAA;EAlDnC;;;EA6DE,GAAA,CACX,GAAA,EAAK,QAAA,EACL,MAAA,OACA,aAAA,GAAgB,QAAA,GAAW,eAAA,GAC1B,OAAA;EA5Da;;;EAuEH,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAxDnB;;;EAmEE,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EApD1B;;;EA6DM,KAAA,IAAK,OAAA;EArDI;;;;;;EAiET,OAAA,IAAW,OAAA;EA9CtB;;;EAqDW,OAAA,IAAO,OAAA;AAAA"}
1
+ {"version":3,"file":"null-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/null-cache-driver.ts"],"mappings":";;;;;cAYa,eAAA,SACH,eAAA,CAAgB,eAAA,EAAiB,sBAAA,aAC9B,WAAA,CAAY,eAAA,EAAiB,sBAAA;;AAF1C;;EAOS,OAAA,EAAS,aAAA;EANQ;;;EAWjB,IAAA;EALS;;;EAUT,IAAA,EAAM,aAAA;EA4BQ;;;EAAA,IAvBV,MAAA;EAoCO;;;cA7BC,OAAA,GAAS,aAAA;EAmDV;;;EA3CX,UAAA,CAAW,OAAA,EAAS,aAAA;EAuDG;;;EA/CvB,QAAA,CAAS,GAAA,EAAK,QAAA;EA+EG;;;;;EArEX,QAAA,CACX,IAAA,EAAM,QAAA,EACN,aAAA,EAAe,QAAA,GAAW,eAAA,EAC1B,QAAA,QAAgB,OAAA,QACf,OAAA;EA1DK;;;EAiEK,eAAA,CAAgB,SAAA,WAAiB,OAAA;EAhEvB;;;EA2EV,GAAA,CACX,GAAA,EAAK,QAAA,EACL,MAAA,OACA,aAAA,GAAgB,QAAA,GAAW,eAAA,GAC1B,OAAA;EArEI;;;EAgFM,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;;;;EAWjB,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EAlEN;;;EA2Ed,KAAA,IAAK,OAAA;EAnEF;;;;;;EA+EH,OAAA,IAAW,OAAA;EAlEN;;;EAyEL,OAAA,IAAO,OAAA;AAAA"}
@@ -29,8 +29,16 @@ var NullCacheDriver = class extends BaseCacheDriver {
29
29
  /**
30
30
  * {@inheritdoc}
31
31
  */
32
- parseKey(_key) {
33
- return "";
32
+ parseKey(key) {
33
+ return super.parseKey(key);
34
+ }
35
+ /**
36
+ * {@inheritdoc}
37
+ *
38
+ * Nothing is stored, so always run the callback and return its own result.
39
+ */
40
+ async remember(_key, _ttlOrOptions, callback) {
41
+ return callback();
34
42
  }
35
43
  /**
36
44
  * {@inheritdoc}
@@ -1 +1 @@
1
- {"version":3,"file":"null-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/null-cache-driver.ts"],"sourcesContent":["import type { GenericObject } from \"@mongez/reinforcements\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type {\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheTtl,\r\n NullCacheDriverOptions,\r\n} from \"../types\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\nexport class NullCacheDriver\r\n extends BaseCacheDriver<NullCacheDriver, NullCacheDriverOptions>\r\n implements CacheDriver<NullCacheDriver, NullCacheDriverOptions>\r\n{\r\n /**\r\n * Options list\r\n */\r\n public options: GenericObject = {};\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"null\";\r\n\r\n /**\r\n * Cached data\r\n */\r\n public data: GenericObject = {};\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get client() {\r\n return this;\r\n }\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor(options: GenericObject = {}) {\r\n super();\r\n this.setOptions(options);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: GenericObject) {\r\n this.options = options;\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public parseKey(_key: CacheKey) {\r\n return \"\";\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n log.info(\"cache\", \"clearing namespace\", namespace);\r\n\r\n log.success(\"cache\", \"namespace cleared\", namespace);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n _value: any,\r\n _ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n log.info(\"cache\", \"setting key\", key);\r\n\r\n log.success(\"cache\", \"key set\", key);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n log.info(\"cache\", \"fetching\", key);\r\n\r\n log.success(\"cache\", \"fetched\", key);\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n log.info(\"cache\", \"removing\", key);\r\n\r\n log.success(\"cache\", \"removed\", key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n log.info(\"cache\", \"flushing\", \"all\");\r\n\r\n log.success(\"cache\", \"flushed\", \"all\");\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * The null driver is a black-hole — `similar()` mirrors `get()` and returns\r\n * an empty result set rather than throwing.\r\n */\r\n public async similar(): Promise<any[]> {\r\n return [];\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n log.success(\"cache\", \"connected\", \"Connected to null cache driver\");\r\n }\r\n}\r\n"],"mappings":";;;;AAWA,IAAa,kBAAb,cACU,gBAEV;;;;CAmBE,IAAW,SAAS;EAClB,OAAO;CACT;;;;CAKA,AAAO,YAAY,UAAyB,CAAC,GAAG;EAC9C,MAAM;iBAvBwB,CAAC;cAKnB;cAKe,CAAC;EAc5B,KAAK,WAAW,OAAO;CACzB;;;;CAKA,AAAO,WAAW,SAAwB;EACxC,KAAK,UAAU;EACf,OAAO;CACT;;;;CAKA,AAAO,SAAS,MAAgB;EAC9B,OAAO;CACT;;;;CAKA,MAAa,gBAAgB,WAAmB;EAC9C,IAAI,KAAK,SAAS,sBAAsB,SAAS;EAEjD,IAAI,QAAQ,SAAS,qBAAqB,SAAS;EAEnD,OAAO;CACT;;;;CAKA,MAAa,IACX,KACA,QACA,eACc;EACd,IAAI,KAAK,SAAS,eAAe,GAAG;EAEpC,IAAI,QAAQ,SAAS,WAAW,GAAG;EAEnC,OAAO;CACT;;;;CAKA,MAAa,IAAI,KAAe;EAC9B,IAAI,KAAK,SAAS,YAAY,GAAG;EAEjC,IAAI,QAAQ,SAAS,WAAW,GAAG;EAEnC,OAAO;CACT;;;;CAKA,MAAa,OAAO,KAAe;EACjC,IAAI,KAAK,SAAS,YAAY,GAAG;EAEjC,IAAI,QAAQ,SAAS,WAAW,GAAG;CACrC;;;;CAKA,MAAa,QAAQ;EACnB,IAAI,KAAK,SAAS,YAAY,KAAK;EAEnC,IAAI,QAAQ,SAAS,WAAW,KAAK;CACvC;;;;;;;CAQA,MAAa,UAA0B;EACrC,OAAO,CAAC;CACV;;;;CAKA,MAAa,UAAU;EACrB,IAAI,QAAQ,SAAS,aAAa,gCAAgC;CACpE;AACF"}
1
+ {"version":3,"file":"null-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/null-cache-driver.ts"],"sourcesContent":["import type { GenericObject } from \"@mongez/reinforcements\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type {\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheTtl,\r\n NullCacheDriverOptions,\r\n RememberOptions,\r\n} from \"../types\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\nexport class NullCacheDriver\r\n extends BaseCacheDriver<NullCacheDriver, NullCacheDriverOptions>\r\n implements CacheDriver<NullCacheDriver, NullCacheDriverOptions>\r\n{\r\n /**\r\n * Options list\r\n */\r\n public options: GenericObject = {};\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"null\";\r\n\r\n /**\r\n * Cached data\r\n */\r\n public data: GenericObject = {};\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get client() {\r\n return this;\r\n }\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor(options: GenericObject = {}) {\r\n super();\r\n this.setOptions(options);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: GenericObject) {\r\n this.options = options;\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public parseKey(key: CacheKey) {\r\n // Real parsed key so the base lock map never merges distinct keys.\r\n return super.parseKey(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Nothing is stored, so always run the callback and return its own result.\r\n */\r\n public async remember(\r\n _key: CacheKey,\r\n _ttlOrOptions: CacheTtl | RememberOptions,\r\n callback: () => Promise<any>,\r\n ): Promise<any> {\r\n return callback();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n log.info(\"cache\", \"clearing namespace\", namespace);\r\n\r\n log.success(\"cache\", \"namespace cleared\", namespace);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n _value: any,\r\n _ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n log.info(\"cache\", \"setting key\", key);\r\n\r\n log.success(\"cache\", \"key set\", key);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n log.info(\"cache\", \"fetching\", key);\r\n\r\n log.success(\"cache\", \"fetched\", key);\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n log.info(\"cache\", \"removing\", key);\r\n\r\n log.success(\"cache\", \"removed\", key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n log.info(\"cache\", \"flushing\", \"all\");\r\n\r\n log.success(\"cache\", \"flushed\", \"all\");\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * The null driver is a black-hole — `similar()` mirrors `get()` and returns\r\n * an empty result set rather than throwing.\r\n */\r\n public async similar(): Promise<any[]> {\r\n return [];\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n log.success(\"cache\", \"connected\", \"Connected to null cache driver\");\r\n }\r\n}\r\n"],"mappings":";;;;AAYA,IAAa,kBAAb,cACU,gBAEV;;;;CAmBE,IAAW,SAAS;EAClB,OAAO;CACT;;;;CAKA,AAAO,YAAY,UAAyB,CAAC,GAAG;EAC9C,MAAM;iBAvBwB,CAAC;cAKnB;cAKe,CAAC;EAc5B,KAAK,WAAW,OAAO;CACzB;;;;CAKA,AAAO,WAAW,SAAwB;EACxC,KAAK,UAAU;EACf,OAAO;CACT;;;;CAKA,AAAO,SAAS,KAAe;EAE7B,OAAO,MAAM,SAAS,GAAG;CAC3B;;;;;;CAOA,MAAa,SACX,MACA,eACA,UACc;EACd,OAAO,SAAS;CAClB;;;;CAKA,MAAa,gBAAgB,WAAmB;EAC9C,IAAI,KAAK,SAAS,sBAAsB,SAAS;EAEjD,IAAI,QAAQ,SAAS,qBAAqB,SAAS;EAEnD,OAAO;CACT;;;;CAKA,MAAa,IACX,KACA,QACA,eACc;EACd,IAAI,KAAK,SAAS,eAAe,GAAG;EAEpC,IAAI,QAAQ,SAAS,WAAW,GAAG;EAEnC,OAAO;CACT;;;;CAKA,MAAa,IAAI,KAAe;EAC9B,IAAI,KAAK,SAAS,YAAY,GAAG;EAEjC,IAAI,QAAQ,SAAS,WAAW,GAAG;EAEnC,OAAO;CACT;;;;CAKA,MAAa,OAAO,KAAe;EACjC,IAAI,KAAK,SAAS,YAAY,GAAG;EAEjC,IAAI,QAAQ,SAAS,WAAW,GAAG;CACrC;;;;CAKA,MAAa,QAAQ;EACnB,IAAI,KAAK,SAAS,YAAY,KAAK;EAEnC,IAAI,QAAQ,SAAS,WAAW,KAAK;CACvC;;;;;;;CAQA,MAAa,UAA0B;EACrC,OAAO,CAAC;CACV;;;;CAKA,MAAa,UAAU;EACrB,IAAI,QAAQ,SAAS,aAAa,gCAAgC;CACpE;AACF"}
@@ -70,10 +70,48 @@ declare class PgCacheDriver extends BaseCacheDriver<PgClientLike, PgCacheOptions
70
70
  */
71
71
  protected get pgClient(): PgClientLike;
72
72
  /**
73
- * Compute an absolute `expires_at` Date for the given relative TTL in seconds,
74
- * or `null` when the entry should not expire (`Infinity` / 0 / undefined).
73
+ * {@inheritdoc}
74
+ *
75
+ * Single `DELETE … WHERE key AND value` — atomic in Postgres.
76
+ */
77
+ protected deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean>;
78
+ /**
79
+ * {@inheritdoc}
80
+ *
81
+ * One `INSERT … ON CONFLICT DO UPDATE` that unions the stored JSON array with
82
+ * `members`. Postgres row-locks the conflicting row, so concurrent adders on
83
+ * any number of servers serialize and none is lost.
84
+ *
85
+ * The index stays a row of the main table holding a JSON array — the same
86
+ * shape earlier versions wrote — so no schema change or migration is needed
87
+ * and existing indexes keep working. (The `tags` column only records tags
88
+ * passed inline to `set()`, not relationships added afterwards, so it can't
89
+ * serve as the invalidation index.)
90
+ */
91
+ tagAdd(tagKey: CacheKey, members: string[]): Promise<void>;
92
+ /**
93
+ * {@inheritdoc}
94
+ *
95
+ * One `UPDATE` that filters `members` out of the stored array (members added
96
+ * concurrently are kept), then a conditional `DELETE` of the row if it ended
97
+ * up empty.
98
+ */
99
+ tagRemove(tagKey: CacheKey, members: string[]): Promise<void>;
100
+ /**
101
+ * TTL in seconds bound to `now() + make_interval(secs => $n)`, so expiry is
102
+ * computed on the DB clock (immune to app/DB clock skew). `null` — from
103
+ * `Infinity` / 0 / undefined — yields a NULL `expires_at` (never expires).
104
+ */
105
+ protected ttlToSeconds(ttl?: number): number | null;
106
+ /**
107
+ * Delete expired rows in batches of `limit`. Returns how many were removed.
108
+ * Also invoked opportunistically (~1 in 200 writes) by `set()`.
109
+ */
110
+ prune(limit?: number): Promise<number>;
111
+ /**
112
+ * Fire-and-forget `prune()` on roughly 1 in 200 writes; errors are logged.
75
113
  */
76
- protected ttlToExpiresAt(ttl?: number): Date | null;
114
+ protected maybePrune(): void;
77
115
  /**
78
116
  * Return the SQL needed to provision the cache table + index. Run once via
79
117
  * the caller's migration tooling — the driver never auto-migrates.
@@ -106,6 +144,42 @@ declare class PgCacheDriver extends BaseCacheDriver<PgClientLike, PgCacheOptions
106
144
  * branch on freshness without going through `get()`'s clone-and-emit path.
107
145
  */
108
146
  protected getEntry(key: CacheKey): Promise<CacheData | null>;
147
+ /**
148
+ * Decode a JSONB column value (node-postgres usually parses it already;
149
+ * some pools hand back a string).
150
+ */
151
+ protected decodeJson(value: unknown): any;
152
+ /**
153
+ * {@inheritdoc}
154
+ *
155
+ * One atomic `INSERT … ON CONFLICT DO UPDATE` statement, so concurrent
156
+ * increments from any number of servers never lose an update. A live row
157
+ * keeps its `expires_at`; a missing or expired row restarts from `value`
158
+ * with the driver default TTL. A non-numeric live value is rejected.
159
+ */
160
+ increment(key: CacheKey, value?: number): Promise<number>;
161
+ /**
162
+ * {@inheritdoc}
163
+ *
164
+ * One atomic `DELETE … RETURNING`, so a value is handed out exactly once
165
+ * across servers. Expired rows count as missing.
166
+ */
167
+ pull(key: CacheKey): Promise<any | null>;
168
+ /**
169
+ * {@inheritdoc}
170
+ *
171
+ * Atomic across servers via optimistic compare-and-set: read the live value,
172
+ * run `fn`, then write with `UPDATE … WHERE value = <what was read>` (or a
173
+ * create-only insert for a missing key), retrying when another writer got
174
+ * there first. `fn` may run more than once under contention; after
175
+ * {@link PG_UPDATE_MAX_ATTEMPTS} lost races it throws {@link CacheError}.
176
+ *
177
+ * Without an explicit `ttl` the row keeps its `expires_at`; a new key gets
178
+ * the driver default TTL. Returning `null` deletes the key (compare-and-delete).
179
+ */
180
+ update<T = any>(key: CacheKey, fn: (current: T | null) => T | null | Promise<T | null>, options?: {
181
+ ttl?: CacheTtl;
182
+ }): Promise<T | null>;
109
183
  /**
110
184
  * {@inheritdoc}
111
185
  */
@@ -1 +1 @@
1
- {"version":3,"file":"pg-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/pg-cache-driver.ts"],"mappings":";;;;;;AAkDA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,aAAA,SACH,eAAA,CAAgB,YAAA,EAAc,cAAA,aAC3B,WAAA,CAAY,YAAA,EAAc,cAAA;EA2eV;;;EAtepB,IAAA;EALI;;;;EAAA,UAWD,WAAA;EAZ4B;;;;;EAmB/B,UAAA,CAAW,OAAA,EAAS,cAAA;EAApB;;;;;;EAAA,UAsCS,iBAAA,IAAqB,OAAA;EAqCvB;;;;;EAAA,UARJ,YAAA,CAAa,MAAA;EAuChB;;;;EAAA,cA/BO,KAAA;EA2FD;;;;EAAA,cAnFC,QAAA,IAAY,YAAA;EAsFE;;;;EAAA,UA9ElB,cAAA,CAAe,GAAA,YAAe,IAAA;EA6MvB;;;;;;;EA9LV,MAAA;EAkSkB;;;EA3PZ,OAAA,IAAO,OAAA;EA4QS;;;;;EA/PhB,UAAA,IAAU,OAAA;EAyTrB;;;EAjTW,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EA+SQ;;;EAjLE,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;;;;;;YAwDd,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;;;;EA4CpC,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;;;;;;;EAiBpB,eAAA,CAAgB,SAAA,WAAiB,OAAA;;;;;;;;EA4BjC,KAAA,IAAK,OAAA;;;;;;;;;;;;;;;;;EA6BL,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;AAAA"}
1
+ {"version":3,"file":"pg-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/pg-cache-driver.ts"],"mappings":";;;;;;AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,aAAA,SACH,eAAA,CAAgB,YAAA,EAAc,cAAA,aAC3B,WAAA,CAAY,YAAA,EAAc,cAAA;EAsfI;;;EAjflC,IAAA;EAqlB2B;;;;EAAA,UA/kBxB,WAAA;EAqnB8B;;;;;EA9mBjC,UAAA,CAAW,OAAA,EAAS,cAAA;EAotBmB;;;;;;EAAA,UA9qB9B,iBAAA,IAAqB,OAAA;EAxD1B;;;;;EAAA,UAqFD,YAAA,CAAa,MAAA;EArFZ;;;;EAAA,cA6FG,KAAA;EA3EP;;;;EAAA,cAmFO,QAAA,IAAY,YAAA;EAhBhB;;;;;EAAA,UAyBM,cAAA,CAAe,GAAA,EAAK,QAAA,EAAU,QAAA,YAAoB,OAAA;EAA9B;;;;;;;;;;;;;EAgCvB,MAAA,CAAO,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAiEhD;;;;;;;EAhCG,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EA+GzC;;;;;EAAA,UA/EV,YAAA,CAAa,GAAA;EAsGrB;;;;EA1FW,KAAA,CAAM,KAAA,YAAe,OAAA;EAmOrB;;;EAAA,UAtNH,UAAA;EA8QM;;;;;;;EA/PT,MAAA;EAgUqB;;;EAzRf,OAAA,IAAO,OAAA;EA4TP;;;;;EA/SA,UAAA,IAAU,OAAA;EAoVhB;;;EA5UM,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAyU0B;;;EAlMhB,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAmMjB;;;;;EAAA,UA3IG,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EA+NxB;;;;EAAA,UAlLf,UAAA,CAAW,KAAA;EAmMyB;;;;;;;;EA/KjC,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EA2O/C;;;AAAiB;;;EAxMf,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;;;;;;;;;;;;;EAoCrB,MAAA,UACX,GAAA,EAAK,QAAA,EACL,EAAA,GAAK,OAAA,EAAS,CAAA,YAAa,CAAA,UAAW,OAAA,CAAQ,CAAA,UAC9C,OAAA;IAAW,GAAA,GAAM,QAAA;EAAA,IAChB,OAAA,CAAQ,CAAA;;;;EAmFE,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;;;;;;;EAiBpB,eAAA,CAAgB,SAAA,WAAiB,OAAA;;;;;;;;EA4BjC,KAAA,IAAK,OAAA;;;;;;;;;;;;;;;;;EA6BL,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;AAAA"}