layercache 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -7
- package/dist/chunk-46UH7LNM.js +312 -0
- package/dist/{chunk-QUB5VZFZ.js → chunk-GF47Y3XR.js} +16 -38
- package/dist/chunk-ZMDB5KOK.js +159 -0
- package/dist/cli.cjs +133 -23
- package/dist/cli.js +66 -4
- package/dist/edge-C1sBhTfv.d.cts +667 -0
- package/dist/edge-C1sBhTfv.d.ts +667 -0
- package/dist/edge.cjs +399 -0
- package/dist/edge.d.cts +2 -0
- package/dist/edge.d.ts +2 -0
- package/dist/edge.js +14 -0
- package/dist/index.cjs +1259 -192
- package/dist/index.d.cts +132 -480
- package/dist/index.d.ts +132 -480
- package/dist/index.js +1115 -474
- package/package.json +7 -2
- package/packages/nestjs/dist/index.cjs +1025 -327
- package/packages/nestjs/dist/index.d.cts +167 -1
- package/packages/nestjs/dist/index.d.ts +167 -1
- package/packages/nestjs/dist/index.js +1013 -325
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
20
30
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
@@ -72,63 +82,260 @@ function Cacheable(options) {
|
|
|
72
82
|
var import_common = require("@nestjs/common");
|
|
73
83
|
|
|
74
84
|
// ../../src/CacheStack.ts
|
|
75
|
-
var import_node_crypto = require("crypto");
|
|
76
85
|
var import_node_events = require("events");
|
|
77
|
-
|
|
86
|
+
|
|
87
|
+
// ../../node_modules/async-mutex/index.mjs
|
|
88
|
+
var E_TIMEOUT = new Error("timeout while waiting for mutex to become available");
|
|
89
|
+
var E_ALREADY_LOCKED = new Error("mutex already locked");
|
|
90
|
+
var E_CANCELED = new Error("request for lock canceled");
|
|
91
|
+
var __awaiter$2 = function(thisArg, _arguments, P, generator) {
|
|
92
|
+
function adopt(value) {
|
|
93
|
+
return value instanceof P ? value : new P(function(resolve) {
|
|
94
|
+
resolve(value);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return new (P || (P = Promise))(function(resolve, reject) {
|
|
98
|
+
function fulfilled(value) {
|
|
99
|
+
try {
|
|
100
|
+
step(generator.next(value));
|
|
101
|
+
} catch (e) {
|
|
102
|
+
reject(e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function rejected(value) {
|
|
106
|
+
try {
|
|
107
|
+
step(generator["throw"](value));
|
|
108
|
+
} catch (e) {
|
|
109
|
+
reject(e);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function step(result) {
|
|
113
|
+
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
114
|
+
}
|
|
115
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
var Semaphore = class {
|
|
119
|
+
constructor(_value, _cancelError = E_CANCELED) {
|
|
120
|
+
this._value = _value;
|
|
121
|
+
this._cancelError = _cancelError;
|
|
122
|
+
this._weightedQueues = [];
|
|
123
|
+
this._weightedWaiters = [];
|
|
124
|
+
}
|
|
125
|
+
acquire(weight = 1) {
|
|
126
|
+
if (weight <= 0)
|
|
127
|
+
throw new Error(`invalid weight ${weight}: must be positive`);
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
if (!this._weightedQueues[weight - 1])
|
|
130
|
+
this._weightedQueues[weight - 1] = [];
|
|
131
|
+
this._weightedQueues[weight - 1].push({ resolve, reject });
|
|
132
|
+
this._dispatch();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
runExclusive(callback, weight = 1) {
|
|
136
|
+
return __awaiter$2(this, void 0, void 0, function* () {
|
|
137
|
+
const [value, release] = yield this.acquire(weight);
|
|
138
|
+
try {
|
|
139
|
+
return yield callback(value);
|
|
140
|
+
} finally {
|
|
141
|
+
release();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
waitForUnlock(weight = 1) {
|
|
146
|
+
if (weight <= 0)
|
|
147
|
+
throw new Error(`invalid weight ${weight}: must be positive`);
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
if (!this._weightedWaiters[weight - 1])
|
|
150
|
+
this._weightedWaiters[weight - 1] = [];
|
|
151
|
+
this._weightedWaiters[weight - 1].push(resolve);
|
|
152
|
+
this._dispatch();
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
isLocked() {
|
|
156
|
+
return this._value <= 0;
|
|
157
|
+
}
|
|
158
|
+
getValue() {
|
|
159
|
+
return this._value;
|
|
160
|
+
}
|
|
161
|
+
setValue(value) {
|
|
162
|
+
this._value = value;
|
|
163
|
+
this._dispatch();
|
|
164
|
+
}
|
|
165
|
+
release(weight = 1) {
|
|
166
|
+
if (weight <= 0)
|
|
167
|
+
throw new Error(`invalid weight ${weight}: must be positive`);
|
|
168
|
+
this._value += weight;
|
|
169
|
+
this._dispatch();
|
|
170
|
+
}
|
|
171
|
+
cancel() {
|
|
172
|
+
this._weightedQueues.forEach((queue) => queue.forEach((entry) => entry.reject(this._cancelError)));
|
|
173
|
+
this._weightedQueues = [];
|
|
174
|
+
}
|
|
175
|
+
_dispatch() {
|
|
176
|
+
var _a;
|
|
177
|
+
for (let weight = this._value; weight > 0; weight--) {
|
|
178
|
+
const queueEntry = (_a = this._weightedQueues[weight - 1]) === null || _a === void 0 ? void 0 : _a.shift();
|
|
179
|
+
if (!queueEntry)
|
|
180
|
+
continue;
|
|
181
|
+
const previousValue = this._value;
|
|
182
|
+
const previousWeight = weight;
|
|
183
|
+
this._value -= weight;
|
|
184
|
+
weight = this._value + 1;
|
|
185
|
+
queueEntry.resolve([previousValue, this._newReleaser(previousWeight)]);
|
|
186
|
+
}
|
|
187
|
+
this._drainUnlockWaiters();
|
|
188
|
+
}
|
|
189
|
+
_newReleaser(weight) {
|
|
190
|
+
let called = false;
|
|
191
|
+
return () => {
|
|
192
|
+
if (called)
|
|
193
|
+
return;
|
|
194
|
+
called = true;
|
|
195
|
+
this.release(weight);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
_drainUnlockWaiters() {
|
|
199
|
+
for (let weight = this._value; weight > 0; weight--) {
|
|
200
|
+
if (!this._weightedWaiters[weight - 1])
|
|
201
|
+
continue;
|
|
202
|
+
this._weightedWaiters[weight - 1].forEach((waiter) => waiter());
|
|
203
|
+
this._weightedWaiters[weight - 1] = [];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
var __awaiter$1 = function(thisArg, _arguments, P, generator) {
|
|
208
|
+
function adopt(value) {
|
|
209
|
+
return value instanceof P ? value : new P(function(resolve) {
|
|
210
|
+
resolve(value);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return new (P || (P = Promise))(function(resolve, reject) {
|
|
214
|
+
function fulfilled(value) {
|
|
215
|
+
try {
|
|
216
|
+
step(generator.next(value));
|
|
217
|
+
} catch (e) {
|
|
218
|
+
reject(e);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function rejected(value) {
|
|
222
|
+
try {
|
|
223
|
+
step(generator["throw"](value));
|
|
224
|
+
} catch (e) {
|
|
225
|
+
reject(e);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function step(result) {
|
|
229
|
+
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
230
|
+
}
|
|
231
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
var Mutex = class {
|
|
235
|
+
constructor(cancelError) {
|
|
236
|
+
this._semaphore = new Semaphore(1, cancelError);
|
|
237
|
+
}
|
|
238
|
+
acquire() {
|
|
239
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
240
|
+
const [, releaser] = yield this._semaphore.acquire();
|
|
241
|
+
return releaser;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
runExclusive(callback) {
|
|
245
|
+
return this._semaphore.runExclusive(() => callback());
|
|
246
|
+
}
|
|
247
|
+
isLocked() {
|
|
248
|
+
return this._semaphore.isLocked();
|
|
249
|
+
}
|
|
250
|
+
waitForUnlock() {
|
|
251
|
+
return this._semaphore.waitForUnlock();
|
|
252
|
+
}
|
|
253
|
+
release() {
|
|
254
|
+
if (this._semaphore.isLocked())
|
|
255
|
+
this._semaphore.release();
|
|
256
|
+
}
|
|
257
|
+
cancel() {
|
|
258
|
+
return this._semaphore.cancel();
|
|
259
|
+
}
|
|
260
|
+
};
|
|
78
261
|
|
|
79
262
|
// ../../src/CacheNamespace.ts
|
|
80
|
-
var CacheNamespace = class {
|
|
263
|
+
var CacheNamespace = class _CacheNamespace {
|
|
81
264
|
constructor(cache, prefix) {
|
|
82
265
|
this.cache = cache;
|
|
83
266
|
this.prefix = prefix;
|
|
84
267
|
}
|
|
85
268
|
cache;
|
|
86
269
|
prefix;
|
|
270
|
+
static metricsMutexes = /* @__PURE__ */ new WeakMap();
|
|
271
|
+
metrics = emptyMetrics();
|
|
87
272
|
async get(key, fetcher, options) {
|
|
88
|
-
return this.cache.get(this.qualify(key), fetcher, options);
|
|
273
|
+
return this.trackMetrics(() => this.cache.get(this.qualify(key), fetcher, options));
|
|
89
274
|
}
|
|
90
275
|
async getOrSet(key, fetcher, options) {
|
|
91
|
-
return this.cache.getOrSet(this.qualify(key), fetcher, options);
|
|
276
|
+
return this.trackMetrics(() => this.cache.getOrSet(this.qualify(key), fetcher, options));
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Like `get()`, but throws `CacheMissError` instead of returning `null`.
|
|
280
|
+
*/
|
|
281
|
+
async getOrThrow(key, fetcher, options) {
|
|
282
|
+
return this.trackMetrics(() => this.cache.getOrThrow(this.qualify(key), fetcher, options));
|
|
92
283
|
}
|
|
93
284
|
async has(key) {
|
|
94
|
-
return this.cache.has(this.qualify(key));
|
|
285
|
+
return this.trackMetrics(() => this.cache.has(this.qualify(key)));
|
|
95
286
|
}
|
|
96
287
|
async ttl(key) {
|
|
97
|
-
return this.cache.ttl(this.qualify(key));
|
|
288
|
+
return this.trackMetrics(() => this.cache.ttl(this.qualify(key)));
|
|
98
289
|
}
|
|
99
290
|
async set(key, value, options) {
|
|
100
|
-
await this.cache.set(this.qualify(key), value, options);
|
|
291
|
+
await this.trackMetrics(() => this.cache.set(this.qualify(key), value, options));
|
|
101
292
|
}
|
|
102
293
|
async delete(key) {
|
|
103
|
-
await this.cache.delete(this.qualify(key));
|
|
294
|
+
await this.trackMetrics(() => this.cache.delete(this.qualify(key)));
|
|
104
295
|
}
|
|
105
296
|
async mdelete(keys) {
|
|
106
|
-
await this.cache.mdelete(keys.map((k) => this.qualify(k)));
|
|
297
|
+
await this.trackMetrics(() => this.cache.mdelete(keys.map((k) => this.qualify(k))));
|
|
107
298
|
}
|
|
108
299
|
async clear() {
|
|
109
|
-
await this.cache.
|
|
300
|
+
await this.trackMetrics(() => this.cache.invalidateByPrefix(this.prefix));
|
|
110
301
|
}
|
|
111
302
|
async mget(entries) {
|
|
112
|
-
return this.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
303
|
+
return this.trackMetrics(
|
|
304
|
+
() => this.cache.mget(
|
|
305
|
+
entries.map((entry) => ({
|
|
306
|
+
...entry,
|
|
307
|
+
key: this.qualify(entry.key)
|
|
308
|
+
}))
|
|
309
|
+
)
|
|
117
310
|
);
|
|
118
311
|
}
|
|
119
312
|
async mset(entries) {
|
|
120
|
-
await this.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
313
|
+
await this.trackMetrics(
|
|
314
|
+
() => this.cache.mset(
|
|
315
|
+
entries.map((entry) => ({
|
|
316
|
+
...entry,
|
|
317
|
+
key: this.qualify(entry.key)
|
|
318
|
+
}))
|
|
319
|
+
)
|
|
125
320
|
);
|
|
126
321
|
}
|
|
127
322
|
async invalidateByTag(tag) {
|
|
128
|
-
await this.cache.invalidateByTag(tag);
|
|
323
|
+
await this.trackMetrics(() => this.cache.invalidateByTag(tag));
|
|
324
|
+
}
|
|
325
|
+
async invalidateByTags(tags, mode = "any") {
|
|
326
|
+
await this.trackMetrics(() => this.cache.invalidateByTags(tags, mode));
|
|
129
327
|
}
|
|
130
328
|
async invalidateByPattern(pattern) {
|
|
131
|
-
await this.cache.invalidateByPattern(this.qualify(pattern));
|
|
329
|
+
await this.trackMetrics(() => this.cache.invalidateByPattern(this.qualify(pattern)));
|
|
330
|
+
}
|
|
331
|
+
async invalidateByPrefix(prefix) {
|
|
332
|
+
await this.trackMetrics(() => this.cache.invalidateByPrefix(this.qualify(prefix)));
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Returns detailed metadata about a single cache key within this namespace.
|
|
336
|
+
*/
|
|
337
|
+
async inspect(key) {
|
|
338
|
+
return this.cache.inspect(this.qualify(key));
|
|
132
339
|
}
|
|
133
340
|
wrap(keyPrefix, fetcher, options) {
|
|
134
341
|
return this.cache.wrap(`${this.prefix}:${keyPrefix}`, fetcher, options);
|
|
@@ -143,15 +350,159 @@ var CacheNamespace = class {
|
|
|
143
350
|
);
|
|
144
351
|
}
|
|
145
352
|
getMetrics() {
|
|
146
|
-
return this.
|
|
353
|
+
return cloneMetrics(this.metrics);
|
|
147
354
|
}
|
|
148
355
|
getHitRate() {
|
|
149
|
-
|
|
356
|
+
const total = this.metrics.hits + this.metrics.misses;
|
|
357
|
+
const overall = total === 0 ? 0 : this.metrics.hits / total;
|
|
358
|
+
const byLayer = {};
|
|
359
|
+
const layers = /* @__PURE__ */ new Set([...Object.keys(this.metrics.hitsByLayer), ...Object.keys(this.metrics.missesByLayer)]);
|
|
360
|
+
for (const layer of layers) {
|
|
361
|
+
const hits = this.metrics.hitsByLayer[layer] ?? 0;
|
|
362
|
+
const misses = this.metrics.missesByLayer[layer] ?? 0;
|
|
363
|
+
byLayer[layer] = hits + misses === 0 ? 0 : hits / (hits + misses);
|
|
364
|
+
}
|
|
365
|
+
return { overall, byLayer };
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Creates a nested namespace. Keys are prefixed with `parentPrefix:childPrefix:`.
|
|
369
|
+
*
|
|
370
|
+
* ```ts
|
|
371
|
+
* const tenant = cache.namespace('tenant:abc')
|
|
372
|
+
* const posts = tenant.namespace('posts')
|
|
373
|
+
* // keys become: "tenant:abc:posts:mykey"
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
namespace(childPrefix) {
|
|
377
|
+
return new _CacheNamespace(this.cache, `${this.prefix}:${childPrefix}`);
|
|
150
378
|
}
|
|
151
379
|
qualify(key) {
|
|
152
380
|
return `${this.prefix}:${key}`;
|
|
153
381
|
}
|
|
382
|
+
async trackMetrics(operation) {
|
|
383
|
+
return this.getMetricsMutex().runExclusive(async () => {
|
|
384
|
+
const before = this.cache.getMetrics();
|
|
385
|
+
const result = await operation();
|
|
386
|
+
const after = this.cache.getMetrics();
|
|
387
|
+
this.metrics = addMetrics(this.metrics, diffMetrics(before, after));
|
|
388
|
+
return result;
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
getMetricsMutex() {
|
|
392
|
+
const existing = _CacheNamespace.metricsMutexes.get(this.cache);
|
|
393
|
+
if (existing) {
|
|
394
|
+
return existing;
|
|
395
|
+
}
|
|
396
|
+
const mutex = new Mutex();
|
|
397
|
+
_CacheNamespace.metricsMutexes.set(this.cache, mutex);
|
|
398
|
+
return mutex;
|
|
399
|
+
}
|
|
154
400
|
};
|
|
401
|
+
function emptyMetrics() {
|
|
402
|
+
return {
|
|
403
|
+
hits: 0,
|
|
404
|
+
misses: 0,
|
|
405
|
+
fetches: 0,
|
|
406
|
+
sets: 0,
|
|
407
|
+
deletes: 0,
|
|
408
|
+
backfills: 0,
|
|
409
|
+
invalidations: 0,
|
|
410
|
+
staleHits: 0,
|
|
411
|
+
refreshes: 0,
|
|
412
|
+
refreshErrors: 0,
|
|
413
|
+
writeFailures: 0,
|
|
414
|
+
singleFlightWaits: 0,
|
|
415
|
+
negativeCacheHits: 0,
|
|
416
|
+
circuitBreakerTrips: 0,
|
|
417
|
+
degradedOperations: 0,
|
|
418
|
+
hitsByLayer: {},
|
|
419
|
+
missesByLayer: {},
|
|
420
|
+
latencyByLayer: {},
|
|
421
|
+
resetAt: Date.now()
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function cloneMetrics(metrics) {
|
|
425
|
+
return {
|
|
426
|
+
...metrics,
|
|
427
|
+
hitsByLayer: { ...metrics.hitsByLayer },
|
|
428
|
+
missesByLayer: { ...metrics.missesByLayer },
|
|
429
|
+
latencyByLayer: Object.fromEntries(
|
|
430
|
+
Object.entries(metrics.latencyByLayer).map(([key, value]) => [key, { ...value }])
|
|
431
|
+
)
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function diffMetrics(before, after) {
|
|
435
|
+
const latencyByLayer = Object.fromEntries(
|
|
436
|
+
Object.entries(after.latencyByLayer).map(([layer, value]) => [
|
|
437
|
+
layer,
|
|
438
|
+
{
|
|
439
|
+
avgMs: value.avgMs,
|
|
440
|
+
maxMs: value.maxMs,
|
|
441
|
+
count: Math.max(0, value.count - (before.latencyByLayer[layer]?.count ?? 0))
|
|
442
|
+
}
|
|
443
|
+
])
|
|
444
|
+
);
|
|
445
|
+
return {
|
|
446
|
+
hits: after.hits - before.hits,
|
|
447
|
+
misses: after.misses - before.misses,
|
|
448
|
+
fetches: after.fetches - before.fetches,
|
|
449
|
+
sets: after.sets - before.sets,
|
|
450
|
+
deletes: after.deletes - before.deletes,
|
|
451
|
+
backfills: after.backfills - before.backfills,
|
|
452
|
+
invalidations: after.invalidations - before.invalidations,
|
|
453
|
+
staleHits: after.staleHits - before.staleHits,
|
|
454
|
+
refreshes: after.refreshes - before.refreshes,
|
|
455
|
+
refreshErrors: after.refreshErrors - before.refreshErrors,
|
|
456
|
+
writeFailures: after.writeFailures - before.writeFailures,
|
|
457
|
+
singleFlightWaits: after.singleFlightWaits - before.singleFlightWaits,
|
|
458
|
+
negativeCacheHits: after.negativeCacheHits - before.negativeCacheHits,
|
|
459
|
+
circuitBreakerTrips: after.circuitBreakerTrips - before.circuitBreakerTrips,
|
|
460
|
+
degradedOperations: after.degradedOperations - before.degradedOperations,
|
|
461
|
+
hitsByLayer: diffMap(before.hitsByLayer, after.hitsByLayer),
|
|
462
|
+
missesByLayer: diffMap(before.missesByLayer, after.missesByLayer),
|
|
463
|
+
latencyByLayer,
|
|
464
|
+
resetAt: after.resetAt
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function addMetrics(base, delta) {
|
|
468
|
+
return {
|
|
469
|
+
hits: base.hits + delta.hits,
|
|
470
|
+
misses: base.misses + delta.misses,
|
|
471
|
+
fetches: base.fetches + delta.fetches,
|
|
472
|
+
sets: base.sets + delta.sets,
|
|
473
|
+
deletes: base.deletes + delta.deletes,
|
|
474
|
+
backfills: base.backfills + delta.backfills,
|
|
475
|
+
invalidations: base.invalidations + delta.invalidations,
|
|
476
|
+
staleHits: base.staleHits + delta.staleHits,
|
|
477
|
+
refreshes: base.refreshes + delta.refreshes,
|
|
478
|
+
refreshErrors: base.refreshErrors + delta.refreshErrors,
|
|
479
|
+
writeFailures: base.writeFailures + delta.writeFailures,
|
|
480
|
+
singleFlightWaits: base.singleFlightWaits + delta.singleFlightWaits,
|
|
481
|
+
negativeCacheHits: base.negativeCacheHits + delta.negativeCacheHits,
|
|
482
|
+
circuitBreakerTrips: base.circuitBreakerTrips + delta.circuitBreakerTrips,
|
|
483
|
+
degradedOperations: base.degradedOperations + delta.degradedOperations,
|
|
484
|
+
hitsByLayer: addMap(base.hitsByLayer, delta.hitsByLayer),
|
|
485
|
+
missesByLayer: addMap(base.missesByLayer, delta.missesByLayer),
|
|
486
|
+
latencyByLayer: cloneMetrics(delta).latencyByLayer,
|
|
487
|
+
resetAt: base.resetAt
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function diffMap(before, after) {
|
|
491
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
492
|
+
const result = {};
|
|
493
|
+
for (const key of keys) {
|
|
494
|
+
result[key] = (after[key] ?? 0) - (before[key] ?? 0);
|
|
495
|
+
}
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
function addMap(base, delta) {
|
|
499
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(delta)]);
|
|
500
|
+
const result = {};
|
|
501
|
+
for (const key of keys) {
|
|
502
|
+
result[key] = (base[key] ?? 0) + (delta[key] ?? 0);
|
|
503
|
+
}
|
|
504
|
+
return result;
|
|
505
|
+
}
|
|
155
506
|
|
|
156
507
|
// ../../src/internal/CircuitBreakerManager.ts
|
|
157
508
|
var CircuitBreakerManager = class {
|
|
@@ -245,11 +596,105 @@ var CircuitBreakerManager = class {
|
|
|
245
596
|
}
|
|
246
597
|
};
|
|
247
598
|
|
|
599
|
+
// ../../src/internal/FetchRateLimiter.ts
|
|
600
|
+
var FetchRateLimiter = class {
|
|
601
|
+
active = 0;
|
|
602
|
+
queue = [];
|
|
603
|
+
startedAt = [];
|
|
604
|
+
drainTimer;
|
|
605
|
+
async schedule(options, task) {
|
|
606
|
+
if (!options) {
|
|
607
|
+
return task();
|
|
608
|
+
}
|
|
609
|
+
const normalized = this.normalize(options);
|
|
610
|
+
if (!normalized) {
|
|
611
|
+
return task();
|
|
612
|
+
}
|
|
613
|
+
return new Promise((resolve, reject) => {
|
|
614
|
+
this.queue.push({ options: normalized, task, resolve, reject });
|
|
615
|
+
this.drain();
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
normalize(options) {
|
|
619
|
+
const maxConcurrent = options.maxConcurrent;
|
|
620
|
+
const intervalMs = options.intervalMs;
|
|
621
|
+
const maxPerInterval = options.maxPerInterval;
|
|
622
|
+
if (!maxConcurrent && !(intervalMs && maxPerInterval)) {
|
|
623
|
+
return void 0;
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
maxConcurrent,
|
|
627
|
+
intervalMs,
|
|
628
|
+
maxPerInterval
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
drain() {
|
|
632
|
+
if (this.drainTimer) {
|
|
633
|
+
clearTimeout(this.drainTimer);
|
|
634
|
+
this.drainTimer = void 0;
|
|
635
|
+
}
|
|
636
|
+
while (this.queue.length > 0) {
|
|
637
|
+
const next = this.queue[0];
|
|
638
|
+
if (!next) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const waitMs = this.waitTime(next.options);
|
|
642
|
+
if (waitMs > 0) {
|
|
643
|
+
this.drainTimer = setTimeout(() => {
|
|
644
|
+
this.drainTimer = void 0;
|
|
645
|
+
this.drain();
|
|
646
|
+
}, waitMs);
|
|
647
|
+
this.drainTimer.unref?.();
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
this.queue.shift();
|
|
651
|
+
this.active += 1;
|
|
652
|
+
this.startedAt.push(Date.now());
|
|
653
|
+
void next.task().then(next.resolve, next.reject).finally(() => {
|
|
654
|
+
this.active -= 1;
|
|
655
|
+
this.drain();
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
waitTime(options) {
|
|
660
|
+
const now = Date.now();
|
|
661
|
+
if (options.maxConcurrent && this.active >= options.maxConcurrent) {
|
|
662
|
+
return 1;
|
|
663
|
+
}
|
|
664
|
+
if (!options.intervalMs || !options.maxPerInterval) {
|
|
665
|
+
return 0;
|
|
666
|
+
}
|
|
667
|
+
this.prune(now, options.intervalMs);
|
|
668
|
+
if (this.startedAt.length < options.maxPerInterval) {
|
|
669
|
+
return 0;
|
|
670
|
+
}
|
|
671
|
+
const oldest = this.startedAt[0];
|
|
672
|
+
if (!oldest) {
|
|
673
|
+
return 0;
|
|
674
|
+
}
|
|
675
|
+
return Math.max(1, options.intervalMs - (now - oldest));
|
|
676
|
+
}
|
|
677
|
+
prune(now, intervalMs) {
|
|
678
|
+
while (this.startedAt.length > 0) {
|
|
679
|
+
const startedAt = this.startedAt[0];
|
|
680
|
+
if (startedAt === void 0 || now - startedAt < intervalMs) {
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
this.startedAt.shift();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
248
688
|
// ../../src/internal/MetricsCollector.ts
|
|
249
689
|
var MetricsCollector = class {
|
|
250
690
|
data = this.empty();
|
|
251
691
|
get snapshot() {
|
|
252
|
-
return {
|
|
692
|
+
return {
|
|
693
|
+
...this.data,
|
|
694
|
+
hitsByLayer: { ...this.data.hitsByLayer },
|
|
695
|
+
missesByLayer: { ...this.data.missesByLayer },
|
|
696
|
+
latencyByLayer: Object.fromEntries(Object.entries(this.data.latencyByLayer).map(([k, v]) => [k, { ...v }]))
|
|
697
|
+
};
|
|
253
698
|
}
|
|
254
699
|
increment(field, amount = 1) {
|
|
255
700
|
;
|
|
@@ -258,6 +703,22 @@ var MetricsCollector = class {
|
|
|
258
703
|
incrementLayer(map, layerName) {
|
|
259
704
|
this.data[map][layerName] = (this.data[map][layerName] ?? 0) + 1;
|
|
260
705
|
}
|
|
706
|
+
/**
|
|
707
|
+
* Records a read latency sample for the given layer.
|
|
708
|
+
* Maintains a rolling average and max using Welford's online algorithm.
|
|
709
|
+
*/
|
|
710
|
+
recordLatency(layerName, durationMs) {
|
|
711
|
+
const existing = this.data.latencyByLayer[layerName];
|
|
712
|
+
if (!existing) {
|
|
713
|
+
this.data.latencyByLayer[layerName] = { avgMs: durationMs, maxMs: durationMs, count: 1 };
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
existing.count += 1;
|
|
717
|
+
existing.avgMs += (durationMs - existing.avgMs) / existing.count;
|
|
718
|
+
if (durationMs > existing.maxMs) {
|
|
719
|
+
existing.maxMs = durationMs;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
261
722
|
reset() {
|
|
262
723
|
this.data = this.empty();
|
|
263
724
|
}
|
|
@@ -292,6 +753,7 @@ var MetricsCollector = class {
|
|
|
292
753
|
degradedOperations: 0,
|
|
293
754
|
hitsByLayer: {},
|
|
294
755
|
missesByLayer: {},
|
|
756
|
+
latencyByLayer: {},
|
|
295
757
|
resetAt: Date.now()
|
|
296
758
|
};
|
|
297
759
|
}
|
|
@@ -419,13 +881,14 @@ var TtlResolver = class {
|
|
|
419
881
|
clearProfiles() {
|
|
420
882
|
this.accessProfiles.clear();
|
|
421
883
|
}
|
|
422
|
-
resolveFreshTtl(key, layerName, kind, options, fallbackTtl, globalNegativeTtl, globalTtl) {
|
|
884
|
+
resolveFreshTtl(key, layerName, kind, options, fallbackTtl, globalNegativeTtl, globalTtl, value) {
|
|
885
|
+
const policyTtl = kind === "value" ? this.resolvePolicyTtl(key, value, options?.ttlPolicy) : void 0;
|
|
423
886
|
const baseTtl = kind === "empty" ? this.resolveLayerSeconds(
|
|
424
887
|
layerName,
|
|
425
888
|
options?.negativeTtl,
|
|
426
889
|
globalNegativeTtl,
|
|
427
|
-
this.resolveLayerSeconds(layerName, options?.ttl, globalTtl, fallbackTtl) ?? DEFAULT_NEGATIVE_TTL_SECONDS
|
|
428
|
-
) : this.resolveLayerSeconds(layerName, options?.ttl, globalTtl, fallbackTtl);
|
|
890
|
+
this.resolveLayerSeconds(layerName, options?.ttl, globalTtl, policyTtl ?? fallbackTtl) ?? DEFAULT_NEGATIVE_TTL_SECONDS
|
|
891
|
+
) : this.resolveLayerSeconds(layerName, options?.ttl, globalTtl, policyTtl ?? fallbackTtl);
|
|
429
892
|
const adaptiveTtl = this.applyAdaptiveTtl(key, layerName, baseTtl, options?.adaptiveTtl);
|
|
430
893
|
const jitter = this.resolveLayerSeconds(layerName, options?.ttlJitter, void 0);
|
|
431
894
|
return this.applyJitter(adaptiveTtl, jitter);
|
|
@@ -464,6 +927,29 @@ var TtlResolver = class {
|
|
|
464
927
|
const delta = (Math.random() * 2 - 1) * jitter;
|
|
465
928
|
return Math.max(1, Math.round(ttl + delta));
|
|
466
929
|
}
|
|
930
|
+
resolvePolicyTtl(key, value, policy) {
|
|
931
|
+
if (!policy) {
|
|
932
|
+
return void 0;
|
|
933
|
+
}
|
|
934
|
+
if (typeof policy === "function") {
|
|
935
|
+
return policy({ key, value });
|
|
936
|
+
}
|
|
937
|
+
const now = /* @__PURE__ */ new Date();
|
|
938
|
+
if (policy === "until-midnight") {
|
|
939
|
+
const nextMidnight = new Date(now);
|
|
940
|
+
nextMidnight.setHours(24, 0, 0, 0);
|
|
941
|
+
return Math.max(1, Math.ceil((nextMidnight.getTime() - now.getTime()) / 1e3));
|
|
942
|
+
}
|
|
943
|
+
if (policy === "next-hour") {
|
|
944
|
+
const nextHour = new Date(now);
|
|
945
|
+
nextHour.setMinutes(60, 0, 0);
|
|
946
|
+
return Math.max(1, Math.ceil((nextHour.getTime() - now.getTime()) / 1e3));
|
|
947
|
+
}
|
|
948
|
+
const alignToSeconds = policy.alignTo;
|
|
949
|
+
const currentSeconds = Math.floor(Date.now() / 1e3);
|
|
950
|
+
const nextBoundary = Math.ceil((currentSeconds + 1) / alignToSeconds) * alignToSeconds;
|
|
951
|
+
return Math.max(1, nextBoundary - currentSeconds);
|
|
952
|
+
}
|
|
467
953
|
readLayerNumber(layerName, value) {
|
|
468
954
|
if (typeof value === "number") {
|
|
469
955
|
return value;
|
|
@@ -491,269 +977,133 @@ var PatternMatcher = class _PatternMatcher {
|
|
|
491
977
|
/**
|
|
492
978
|
* Tests whether a glob-style pattern matches a value.
|
|
493
979
|
* Supports `*` (any sequence of characters) and `?` (any single character).
|
|
494
|
-
* Uses a
|
|
980
|
+
* Uses a two-pointer algorithm to avoid ReDoS vulnerabilities and
|
|
981
|
+
* quadratic memory usage on long patterns/keys.
|
|
495
982
|
*/
|
|
496
983
|
static matches(pattern, value) {
|
|
497
984
|
return _PatternMatcher.matchLinear(pattern, value);
|
|
498
985
|
}
|
|
499
986
|
/**
|
|
500
|
-
* Linear-time glob matching
|
|
501
|
-
* Avoids catastrophic backtracking that RegExp-based glob matching can cause.
|
|
987
|
+
* Linear-time glob matching with O(1) extra memory.
|
|
502
988
|
*/
|
|
503
989
|
static matchLinear(pattern, value) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const pc = pattern[i - 1];
|
|
516
|
-
if (pc === "*") {
|
|
517
|
-
dp[i][j] = dp[i - 1]?.[j] || dp[i]?.[j - 1];
|
|
518
|
-
} else if (pc === "?" || pc === value[j - 1]) {
|
|
519
|
-
dp[i][j] = dp[i - 1]?.[j - 1];
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
return dp[m]?.[n];
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
// ../../src/invalidation/TagIndex.ts
|
|
528
|
-
var TagIndex = class {
|
|
529
|
-
tagToKeys = /* @__PURE__ */ new Map();
|
|
530
|
-
keyToTags = /* @__PURE__ */ new Map();
|
|
531
|
-
knownKeys = /* @__PURE__ */ new Set();
|
|
532
|
-
async touch(key) {
|
|
533
|
-
this.knownKeys.add(key);
|
|
534
|
-
}
|
|
535
|
-
async track(key, tags) {
|
|
536
|
-
this.knownKeys.add(key);
|
|
537
|
-
if (tags.length === 0) {
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
const existingTags = this.keyToTags.get(key);
|
|
541
|
-
if (existingTags) {
|
|
542
|
-
for (const tag of existingTags) {
|
|
543
|
-
this.tagToKeys.get(tag)?.delete(key);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
const tagSet = new Set(tags);
|
|
547
|
-
this.keyToTags.set(key, tagSet);
|
|
548
|
-
for (const tag of tagSet) {
|
|
549
|
-
const keys = this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set();
|
|
550
|
-
keys.add(key);
|
|
551
|
-
this.tagToKeys.set(tag, keys);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
async remove(key) {
|
|
555
|
-
this.knownKeys.delete(key);
|
|
556
|
-
const tags = this.keyToTags.get(key);
|
|
557
|
-
if (!tags) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
for (const tag of tags) {
|
|
561
|
-
const keys = this.tagToKeys.get(tag);
|
|
562
|
-
if (!keys) {
|
|
990
|
+
let patternIndex = 0;
|
|
991
|
+
let valueIndex = 0;
|
|
992
|
+
let starIndex = -1;
|
|
993
|
+
let backtrackValueIndex = 0;
|
|
994
|
+
while (valueIndex < value.length) {
|
|
995
|
+
const patternChar = pattern[patternIndex];
|
|
996
|
+
const valueChar = value[valueIndex];
|
|
997
|
+
if (patternChar === "*" && patternIndex < pattern.length) {
|
|
998
|
+
starIndex = patternIndex;
|
|
999
|
+
patternIndex += 1;
|
|
1000
|
+
backtrackValueIndex = valueIndex;
|
|
563
1001
|
continue;
|
|
564
1002
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
this.keyToTags.delete(key);
|
|
571
|
-
}
|
|
572
|
-
async keysForTag(tag) {
|
|
573
|
-
return [...this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()];
|
|
574
|
-
}
|
|
575
|
-
async matchPattern(pattern) {
|
|
576
|
-
return [...this.knownKeys].filter((key) => PatternMatcher.matches(pattern, key));
|
|
577
|
-
}
|
|
578
|
-
async clear() {
|
|
579
|
-
this.tagToKeys.clear();
|
|
580
|
-
this.keyToTags.clear();
|
|
581
|
-
this.knownKeys.clear();
|
|
582
|
-
}
|
|
583
|
-
};
|
|
584
|
-
|
|
585
|
-
// ../../node_modules/async-mutex/index.mjs
|
|
586
|
-
var E_TIMEOUT = new Error("timeout while waiting for mutex to become available");
|
|
587
|
-
var E_ALREADY_LOCKED = new Error("mutex already locked");
|
|
588
|
-
var E_CANCELED = new Error("request for lock canceled");
|
|
589
|
-
var __awaiter$2 = function(thisArg, _arguments, P, generator) {
|
|
590
|
-
function adopt(value) {
|
|
591
|
-
return value instanceof P ? value : new P(function(resolve) {
|
|
592
|
-
resolve(value);
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
return new (P || (P = Promise))(function(resolve, reject) {
|
|
596
|
-
function fulfilled(value) {
|
|
597
|
-
try {
|
|
598
|
-
step(generator.next(value));
|
|
599
|
-
} catch (e) {
|
|
600
|
-
reject(e);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
function rejected(value) {
|
|
604
|
-
try {
|
|
605
|
-
step(generator["throw"](value));
|
|
606
|
-
} catch (e) {
|
|
607
|
-
reject(e);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
function step(result) {
|
|
611
|
-
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
612
|
-
}
|
|
613
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
614
|
-
});
|
|
615
|
-
};
|
|
616
|
-
var Semaphore = class {
|
|
617
|
-
constructor(_value, _cancelError = E_CANCELED) {
|
|
618
|
-
this._value = _value;
|
|
619
|
-
this._cancelError = _cancelError;
|
|
620
|
-
this._weightedQueues = [];
|
|
621
|
-
this._weightedWaiters = [];
|
|
622
|
-
}
|
|
623
|
-
acquire(weight = 1) {
|
|
624
|
-
if (weight <= 0)
|
|
625
|
-
throw new Error(`invalid weight ${weight}: must be positive`);
|
|
626
|
-
return new Promise((resolve, reject) => {
|
|
627
|
-
if (!this._weightedQueues[weight - 1])
|
|
628
|
-
this._weightedQueues[weight - 1] = [];
|
|
629
|
-
this._weightedQueues[weight - 1].push({ resolve, reject });
|
|
630
|
-
this._dispatch();
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
runExclusive(callback, weight = 1) {
|
|
634
|
-
return __awaiter$2(this, void 0, void 0, function* () {
|
|
635
|
-
const [value, release] = yield this.acquire(weight);
|
|
636
|
-
try {
|
|
637
|
-
return yield callback(value);
|
|
638
|
-
} finally {
|
|
639
|
-
release();
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
waitForUnlock(weight = 1) {
|
|
644
|
-
if (weight <= 0)
|
|
645
|
-
throw new Error(`invalid weight ${weight}: must be positive`);
|
|
646
|
-
return new Promise((resolve) => {
|
|
647
|
-
if (!this._weightedWaiters[weight - 1])
|
|
648
|
-
this._weightedWaiters[weight - 1] = [];
|
|
649
|
-
this._weightedWaiters[weight - 1].push(resolve);
|
|
650
|
-
this._dispatch();
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
isLocked() {
|
|
654
|
-
return this._value <= 0;
|
|
655
|
-
}
|
|
656
|
-
getValue() {
|
|
657
|
-
return this._value;
|
|
658
|
-
}
|
|
659
|
-
setValue(value) {
|
|
660
|
-
this._value = value;
|
|
661
|
-
this._dispatch();
|
|
662
|
-
}
|
|
663
|
-
release(weight = 1) {
|
|
664
|
-
if (weight <= 0)
|
|
665
|
-
throw new Error(`invalid weight ${weight}: must be positive`);
|
|
666
|
-
this._value += weight;
|
|
667
|
-
this._dispatch();
|
|
668
|
-
}
|
|
669
|
-
cancel() {
|
|
670
|
-
this._weightedQueues.forEach((queue) => queue.forEach((entry) => entry.reject(this._cancelError)));
|
|
671
|
-
this._weightedQueues = [];
|
|
672
|
-
}
|
|
673
|
-
_dispatch() {
|
|
674
|
-
var _a;
|
|
675
|
-
for (let weight = this._value; weight > 0; weight--) {
|
|
676
|
-
const queueEntry = (_a = this._weightedQueues[weight - 1]) === null || _a === void 0 ? void 0 : _a.shift();
|
|
677
|
-
if (!queueEntry)
|
|
1003
|
+
if (patternChar === "?" || patternChar === valueChar) {
|
|
1004
|
+
patternIndex += 1;
|
|
1005
|
+
valueIndex += 1;
|
|
678
1006
|
continue;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
this._drainUnlockWaiters();
|
|
686
|
-
}
|
|
687
|
-
_newReleaser(weight) {
|
|
688
|
-
let called = false;
|
|
689
|
-
return () => {
|
|
690
|
-
if (called)
|
|
691
|
-
return;
|
|
692
|
-
called = true;
|
|
693
|
-
this.release(weight);
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
_drainUnlockWaiters() {
|
|
697
|
-
for (let weight = this._value; weight > 0; weight--) {
|
|
698
|
-
if (!this._weightedWaiters[weight - 1])
|
|
1007
|
+
}
|
|
1008
|
+
if (starIndex !== -1) {
|
|
1009
|
+
patternIndex = starIndex + 1;
|
|
1010
|
+
backtrackValueIndex += 1;
|
|
1011
|
+
valueIndex = backtrackValueIndex;
|
|
699
1012
|
continue;
|
|
700
|
-
|
|
701
|
-
|
|
1013
|
+
}
|
|
1014
|
+
return false;
|
|
702
1015
|
}
|
|
1016
|
+
while (patternIndex < pattern.length && pattern[patternIndex] === "*") {
|
|
1017
|
+
patternIndex += 1;
|
|
1018
|
+
}
|
|
1019
|
+
return patternIndex === pattern.length;
|
|
703
1020
|
}
|
|
704
1021
|
};
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1022
|
+
|
|
1023
|
+
// ../../src/invalidation/TagIndex.ts
|
|
1024
|
+
var TagIndex = class {
|
|
1025
|
+
tagToKeys = /* @__PURE__ */ new Map();
|
|
1026
|
+
keyToTags = /* @__PURE__ */ new Map();
|
|
1027
|
+
knownKeys = /* @__PURE__ */ new Set();
|
|
1028
|
+
maxKnownKeys;
|
|
1029
|
+
constructor(options = {}) {
|
|
1030
|
+
this.maxKnownKeys = options.maxKnownKeys;
|
|
710
1031
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1032
|
+
async touch(key) {
|
|
1033
|
+
this.knownKeys.add(key);
|
|
1034
|
+
this.pruneKnownKeysIfNeeded();
|
|
1035
|
+
}
|
|
1036
|
+
async track(key, tags) {
|
|
1037
|
+
this.knownKeys.add(key);
|
|
1038
|
+
this.pruneKnownKeysIfNeeded();
|
|
1039
|
+
if (tags.length === 0) {
|
|
1040
|
+
return;
|
|
718
1041
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
reject(e);
|
|
1042
|
+
const existingTags = this.keyToTags.get(key);
|
|
1043
|
+
if (existingTags) {
|
|
1044
|
+
for (const tag of existingTags) {
|
|
1045
|
+
this.tagToKeys.get(tag)?.delete(key);
|
|
724
1046
|
}
|
|
725
1047
|
}
|
|
726
|
-
|
|
727
|
-
|
|
1048
|
+
const tagSet = new Set(tags);
|
|
1049
|
+
this.keyToTags.set(key, tagSet);
|
|
1050
|
+
for (const tag of tagSet) {
|
|
1051
|
+
const keys = this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set();
|
|
1052
|
+
keys.add(key);
|
|
1053
|
+
this.tagToKeys.set(tag, keys);
|
|
728
1054
|
}
|
|
729
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
730
|
-
});
|
|
731
|
-
};
|
|
732
|
-
var Mutex = class {
|
|
733
|
-
constructor(cancelError) {
|
|
734
|
-
this._semaphore = new Semaphore(1, cancelError);
|
|
735
1055
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const [, releaser] = yield this._semaphore.acquire();
|
|
739
|
-
return releaser;
|
|
740
|
-
});
|
|
1056
|
+
async remove(key) {
|
|
1057
|
+
this.removeKey(key);
|
|
741
1058
|
}
|
|
742
|
-
|
|
743
|
-
return this.
|
|
1059
|
+
async keysForTag(tag) {
|
|
1060
|
+
return [...this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()];
|
|
744
1061
|
}
|
|
745
|
-
|
|
746
|
-
return this.
|
|
1062
|
+
async keysForPrefix(prefix) {
|
|
1063
|
+
return [...this.knownKeys].filter((key) => key.startsWith(prefix));
|
|
747
1064
|
}
|
|
748
|
-
|
|
749
|
-
return this.
|
|
1065
|
+
async tagsForKey(key) {
|
|
1066
|
+
return [...this.keyToTags.get(key) ?? /* @__PURE__ */ new Set()];
|
|
750
1067
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
this._semaphore.release();
|
|
1068
|
+
async matchPattern(pattern) {
|
|
1069
|
+
return [...this.knownKeys].filter((key) => PatternMatcher.matches(pattern, key));
|
|
754
1070
|
}
|
|
755
|
-
|
|
756
|
-
|
|
1071
|
+
async clear() {
|
|
1072
|
+
this.tagToKeys.clear();
|
|
1073
|
+
this.keyToTags.clear();
|
|
1074
|
+
this.knownKeys.clear();
|
|
1075
|
+
}
|
|
1076
|
+
pruneKnownKeysIfNeeded() {
|
|
1077
|
+
if (this.maxKnownKeys === void 0 || this.knownKeys.size <= this.maxKnownKeys) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
const toRemove = Math.ceil(this.maxKnownKeys * 0.1);
|
|
1081
|
+
let removed = 0;
|
|
1082
|
+
for (const key of this.knownKeys) {
|
|
1083
|
+
if (removed >= toRemove) {
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
this.removeKey(key);
|
|
1087
|
+
removed += 1;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
removeKey(key) {
|
|
1091
|
+
this.knownKeys.delete(key);
|
|
1092
|
+
const tags = this.keyToTags.get(key);
|
|
1093
|
+
if (!tags) {
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
for (const tag of tags) {
|
|
1097
|
+
const keys = this.tagToKeys.get(tag);
|
|
1098
|
+
if (!keys) {
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
keys.delete(key);
|
|
1102
|
+
if (keys.size === 0) {
|
|
1103
|
+
this.tagToKeys.delete(tag);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
this.keyToTags.delete(key);
|
|
757
1107
|
}
|
|
758
1108
|
};
|
|
759
1109
|
|
|
@@ -782,6 +1132,16 @@ var StampedeGuard = class {
|
|
|
782
1132
|
}
|
|
783
1133
|
};
|
|
784
1134
|
|
|
1135
|
+
// ../../src/types.ts
|
|
1136
|
+
var CacheMissError = class extends Error {
|
|
1137
|
+
key;
|
|
1138
|
+
constructor(key) {
|
|
1139
|
+
super(`Cache miss for key "${key}".`);
|
|
1140
|
+
this.name = "CacheMissError";
|
|
1141
|
+
this.key = key;
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
|
|
785
1145
|
// ../../src/CacheStack.ts
|
|
786
1146
|
var DEFAULT_SINGLE_FLIGHT_LEASE_MS = 3e4;
|
|
787
1147
|
var DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS = 5e3;
|
|
@@ -825,6 +1185,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
825
1185
|
const maxProfileEntries = options.maxProfileEntries ?? DEFAULT_MAX_PROFILE_ENTRIES;
|
|
826
1186
|
this.ttlResolver = new TtlResolver({ maxProfileEntries });
|
|
827
1187
|
this.circuitBreakerManager = new CircuitBreakerManager({ maxEntries: maxProfileEntries });
|
|
1188
|
+
this.currentGeneration = options.generation;
|
|
828
1189
|
if (options.publishSetInvalidation !== void 0) {
|
|
829
1190
|
console.warn(
|
|
830
1191
|
"[layercache] CacheStackOptions.publishSetInvalidation is deprecated. Use broadcastL1Invalidation instead."
|
|
@@ -833,21 +1194,27 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
833
1194
|
const debugEnv = process.env.DEBUG?.split(",").includes("layercache:debug") ?? false;
|
|
834
1195
|
this.logger = typeof options.logger === "object" ? options.logger : new DebugLogger(Boolean(options.logger) || debugEnv);
|
|
835
1196
|
this.tagIndex = options.tagIndex ?? new TagIndex();
|
|
1197
|
+
this.initializeWriteBehind(options.writeBehind);
|
|
836
1198
|
this.startup = this.initialize();
|
|
837
1199
|
}
|
|
838
1200
|
layers;
|
|
839
1201
|
options;
|
|
840
1202
|
stampedeGuard = new StampedeGuard();
|
|
841
1203
|
metricsCollector = new MetricsCollector();
|
|
842
|
-
instanceId = (
|
|
1204
|
+
instanceId = createInstanceId();
|
|
843
1205
|
startup;
|
|
844
1206
|
unsubscribeInvalidation;
|
|
845
1207
|
logger;
|
|
846
1208
|
tagIndex;
|
|
1209
|
+
fetchRateLimiter = new FetchRateLimiter();
|
|
847
1210
|
backgroundRefreshes = /* @__PURE__ */ new Map();
|
|
848
1211
|
layerDegradedUntil = /* @__PURE__ */ new Map();
|
|
849
1212
|
ttlResolver;
|
|
850
1213
|
circuitBreakerManager;
|
|
1214
|
+
currentGeneration;
|
|
1215
|
+
writeBehindQueue = [];
|
|
1216
|
+
writeBehindTimer;
|
|
1217
|
+
writeBehindFlushPromise;
|
|
851
1218
|
isDisconnecting = false;
|
|
852
1219
|
disconnectPromise;
|
|
853
1220
|
/**
|
|
@@ -857,9 +1224,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
857
1224
|
* and no `fetcher` is provided.
|
|
858
1225
|
*/
|
|
859
1226
|
async get(key, fetcher, options) {
|
|
860
|
-
const normalizedKey = this.validateCacheKey(key);
|
|
1227
|
+
const normalizedKey = this.qualifyKey(this.validateCacheKey(key));
|
|
861
1228
|
this.validateWriteOptions(options);
|
|
862
|
-
await this.
|
|
1229
|
+
await this.awaitStartup("get");
|
|
863
1230
|
const hit = await this.readFromLayers(normalizedKey, options, "allow-stale");
|
|
864
1231
|
if (hit.found) {
|
|
865
1232
|
this.ttlResolver.recordAccess(normalizedKey);
|
|
@@ -908,12 +1275,24 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
908
1275
|
async getOrSet(key, fetcher, options) {
|
|
909
1276
|
return this.get(key, fetcher, options);
|
|
910
1277
|
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Like `get()`, but throws `CacheMissError` instead of returning `null`.
|
|
1280
|
+
* Useful when the value is expected to exist or the fetcher is expected to
|
|
1281
|
+
* return non-null.
|
|
1282
|
+
*/
|
|
1283
|
+
async getOrThrow(key, fetcher, options) {
|
|
1284
|
+
const value = await this.get(key, fetcher, options);
|
|
1285
|
+
if (value === null) {
|
|
1286
|
+
throw new CacheMissError(key);
|
|
1287
|
+
}
|
|
1288
|
+
return value;
|
|
1289
|
+
}
|
|
911
1290
|
/**
|
|
912
1291
|
* Returns true if the given key exists and is not expired in any layer.
|
|
913
1292
|
*/
|
|
914
1293
|
async has(key) {
|
|
915
|
-
const normalizedKey = this.validateCacheKey(key);
|
|
916
|
-
await this.
|
|
1294
|
+
const normalizedKey = this.qualifyKey(this.validateCacheKey(key));
|
|
1295
|
+
await this.awaitStartup("has");
|
|
917
1296
|
for (const layer of this.layers) {
|
|
918
1297
|
if (this.shouldSkipLayer(layer)) {
|
|
919
1298
|
continue;
|
|
@@ -943,8 +1322,8 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
943
1322
|
* that has it, or null if the key is not found / has no TTL.
|
|
944
1323
|
*/
|
|
945
1324
|
async ttl(key) {
|
|
946
|
-
const normalizedKey = this.validateCacheKey(key);
|
|
947
|
-
await this.
|
|
1325
|
+
const normalizedKey = this.qualifyKey(this.validateCacheKey(key));
|
|
1326
|
+
await this.awaitStartup("ttl");
|
|
948
1327
|
for (const layer of this.layers) {
|
|
949
1328
|
if (this.shouldSkipLayer(layer)) {
|
|
950
1329
|
continue;
|
|
@@ -965,17 +1344,17 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
965
1344
|
* Stores a value in all cache layers. Overwrites any existing value.
|
|
966
1345
|
*/
|
|
967
1346
|
async set(key, value, options) {
|
|
968
|
-
const normalizedKey = this.validateCacheKey(key);
|
|
1347
|
+
const normalizedKey = this.qualifyKey(this.validateCacheKey(key));
|
|
969
1348
|
this.validateWriteOptions(options);
|
|
970
|
-
await this.
|
|
1349
|
+
await this.awaitStartup("set");
|
|
971
1350
|
await this.storeEntry(normalizedKey, "value", value, options);
|
|
972
1351
|
}
|
|
973
1352
|
/**
|
|
974
1353
|
* Deletes the key from all layers and publishes an invalidation message.
|
|
975
1354
|
*/
|
|
976
1355
|
async delete(key) {
|
|
977
|
-
const normalizedKey = this.validateCacheKey(key);
|
|
978
|
-
await this.
|
|
1356
|
+
const normalizedKey = this.qualifyKey(this.validateCacheKey(key));
|
|
1357
|
+
await this.awaitStartup("delete");
|
|
979
1358
|
await this.deleteKeys([normalizedKey]);
|
|
980
1359
|
await this.publishInvalidation({
|
|
981
1360
|
scope: "key",
|
|
@@ -985,7 +1364,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
985
1364
|
});
|
|
986
1365
|
}
|
|
987
1366
|
async clear() {
|
|
988
|
-
await this.
|
|
1367
|
+
await this.awaitStartup("clear");
|
|
989
1368
|
await Promise.all(this.layers.map((layer) => layer.clear()));
|
|
990
1369
|
await this.tagIndex.clear();
|
|
991
1370
|
this.ttlResolver.clearProfiles();
|
|
@@ -1001,23 +1380,25 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1001
1380
|
if (keys.length === 0) {
|
|
1002
1381
|
return;
|
|
1003
1382
|
}
|
|
1004
|
-
await this.
|
|
1383
|
+
await this.awaitStartup("mdelete");
|
|
1005
1384
|
const normalizedKeys = keys.map((k) => this.validateCacheKey(k));
|
|
1006
|
-
|
|
1385
|
+
const cacheKeys = normalizedKeys.map((key) => this.qualifyKey(key));
|
|
1386
|
+
await this.deleteKeys(cacheKeys);
|
|
1007
1387
|
await this.publishInvalidation({
|
|
1008
1388
|
scope: "keys",
|
|
1009
|
-
keys:
|
|
1389
|
+
keys: cacheKeys,
|
|
1010
1390
|
sourceId: this.instanceId,
|
|
1011
1391
|
operation: "delete"
|
|
1012
1392
|
});
|
|
1013
1393
|
}
|
|
1014
1394
|
async mget(entries) {
|
|
1395
|
+
this.assertActive("mget");
|
|
1015
1396
|
if (entries.length === 0) {
|
|
1016
1397
|
return [];
|
|
1017
1398
|
}
|
|
1018
1399
|
const normalizedEntries = entries.map((entry) => ({
|
|
1019
1400
|
...entry,
|
|
1020
|
-
key: this.validateCacheKey(entry.key)
|
|
1401
|
+
key: this.qualifyKey(this.validateCacheKey(entry.key))
|
|
1021
1402
|
}));
|
|
1022
1403
|
normalizedEntries.forEach((entry) => this.validateWriteOptions(entry.options));
|
|
1023
1404
|
const canFastPath = normalizedEntries.every((entry) => entry.fetch === void 0 && entry.options === void 0);
|
|
@@ -1043,7 +1424,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1043
1424
|
})
|
|
1044
1425
|
);
|
|
1045
1426
|
}
|
|
1046
|
-
await this.
|
|
1427
|
+
await this.awaitStartup("mget");
|
|
1047
1428
|
const pending = /* @__PURE__ */ new Set();
|
|
1048
1429
|
const indexesByKey = /* @__PURE__ */ new Map();
|
|
1049
1430
|
const resultsByKey = /* @__PURE__ */ new Map();
|
|
@@ -1091,14 +1472,17 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1091
1472
|
return normalizedEntries.map((entry) => resultsByKey.get(entry.key) ?? null);
|
|
1092
1473
|
}
|
|
1093
1474
|
async mset(entries) {
|
|
1475
|
+
this.assertActive("mset");
|
|
1094
1476
|
const normalizedEntries = entries.map((entry) => ({
|
|
1095
1477
|
...entry,
|
|
1096
|
-
key: this.validateCacheKey(entry.key)
|
|
1478
|
+
key: this.qualifyKey(this.validateCacheKey(entry.key))
|
|
1097
1479
|
}));
|
|
1098
1480
|
normalizedEntries.forEach((entry) => this.validateWriteOptions(entry.options));
|
|
1099
|
-
await
|
|
1481
|
+
await this.awaitStartup("mset");
|
|
1482
|
+
await this.writeBatch(normalizedEntries);
|
|
1100
1483
|
}
|
|
1101
1484
|
async warm(entries, options = {}) {
|
|
1485
|
+
this.assertActive("warm");
|
|
1102
1486
|
const concurrency = Math.max(1, options.concurrency ?? 4);
|
|
1103
1487
|
const total = entries.length;
|
|
1104
1488
|
let completed = 0;
|
|
@@ -1147,14 +1531,31 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1147
1531
|
return new CacheNamespace(this, prefix);
|
|
1148
1532
|
}
|
|
1149
1533
|
async invalidateByTag(tag) {
|
|
1150
|
-
await this.
|
|
1534
|
+
await this.awaitStartup("invalidateByTag");
|
|
1151
1535
|
const keys = await this.tagIndex.keysForTag(tag);
|
|
1152
1536
|
await this.deleteKeys(keys);
|
|
1153
1537
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1154
1538
|
}
|
|
1539
|
+
async invalidateByTags(tags, mode = "any") {
|
|
1540
|
+
if (tags.length === 0) {
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
await this.awaitStartup("invalidateByTags");
|
|
1544
|
+
const keysByTag = await Promise.all(tags.map((tag) => this.tagIndex.keysForTag(tag)));
|
|
1545
|
+
const keys = mode === "all" ? this.intersectKeys(keysByTag) : [...new Set(keysByTag.flat())];
|
|
1546
|
+
await this.deleteKeys(keys);
|
|
1547
|
+
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1548
|
+
}
|
|
1155
1549
|
async invalidateByPattern(pattern) {
|
|
1156
|
-
await this.
|
|
1157
|
-
const keys = await this.tagIndex.matchPattern(pattern);
|
|
1550
|
+
await this.awaitStartup("invalidateByPattern");
|
|
1551
|
+
const keys = await this.tagIndex.matchPattern(this.qualifyPattern(pattern));
|
|
1552
|
+
await this.deleteKeys(keys);
|
|
1553
|
+
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1554
|
+
}
|
|
1555
|
+
async invalidateByPrefix(prefix) {
|
|
1556
|
+
await this.awaitStartup("invalidateByPrefix");
|
|
1557
|
+
const qualifiedPrefix = this.qualifyKey(this.validateCacheKey(prefix));
|
|
1558
|
+
const keys = this.tagIndex.keysForPrefix ? await this.tagIndex.keysForPrefix(qualifiedPrefix) : await this.tagIndex.matchPattern(`${qualifiedPrefix}*`);
|
|
1158
1559
|
await this.deleteKeys(keys);
|
|
1159
1560
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1160
1561
|
}
|
|
@@ -1181,8 +1582,77 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1181
1582
|
getHitRate() {
|
|
1182
1583
|
return this.metricsCollector.hitRate();
|
|
1183
1584
|
}
|
|
1184
|
-
async
|
|
1585
|
+
async healthCheck() {
|
|
1185
1586
|
await this.startup;
|
|
1587
|
+
return Promise.all(
|
|
1588
|
+
this.layers.map(async (layer) => {
|
|
1589
|
+
const startedAt = performance.now();
|
|
1590
|
+
try {
|
|
1591
|
+
const healthy = layer.ping ? await layer.ping() : true;
|
|
1592
|
+
return {
|
|
1593
|
+
layer: layer.name,
|
|
1594
|
+
healthy,
|
|
1595
|
+
latencyMs: performance.now() - startedAt
|
|
1596
|
+
};
|
|
1597
|
+
} catch (error) {
|
|
1598
|
+
return {
|
|
1599
|
+
layer: layer.name,
|
|
1600
|
+
healthy: false,
|
|
1601
|
+
latencyMs: performance.now() - startedAt,
|
|
1602
|
+
error: this.formatError(error)
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
})
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
bumpGeneration(nextGeneration) {
|
|
1609
|
+
const current = this.currentGeneration ?? 0;
|
|
1610
|
+
this.currentGeneration = nextGeneration ?? current + 1;
|
|
1611
|
+
return this.currentGeneration;
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Returns detailed metadata about a single cache key: which layers contain it,
|
|
1615
|
+
* remaining fresh/stale/error TTLs, and associated tags.
|
|
1616
|
+
* Returns `null` if the key does not exist in any layer.
|
|
1617
|
+
*/
|
|
1618
|
+
async inspect(key) {
|
|
1619
|
+
const userKey = this.validateCacheKey(key);
|
|
1620
|
+
const normalizedKey = this.qualifyKey(userKey);
|
|
1621
|
+
await this.awaitStartup("inspect");
|
|
1622
|
+
const foundInLayers = [];
|
|
1623
|
+
let freshTtlSeconds = null;
|
|
1624
|
+
let staleTtlSeconds = null;
|
|
1625
|
+
let errorTtlSeconds = null;
|
|
1626
|
+
let isStale = false;
|
|
1627
|
+
for (const layer of this.layers) {
|
|
1628
|
+
if (this.shouldSkipLayer(layer)) {
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
const stored = await this.readLayerEntry(layer, normalizedKey);
|
|
1632
|
+
if (stored === null) {
|
|
1633
|
+
continue;
|
|
1634
|
+
}
|
|
1635
|
+
const resolved = resolveStoredValue(stored);
|
|
1636
|
+
if (resolved.state === "expired") {
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
foundInLayers.push(layer.name);
|
|
1640
|
+
if (foundInLayers.length === 1 && resolved.envelope) {
|
|
1641
|
+
const now = Date.now();
|
|
1642
|
+
freshTtlSeconds = resolved.envelope.freshUntil !== null ? Math.max(0, Math.ceil((resolved.envelope.freshUntil - now) / 1e3)) : null;
|
|
1643
|
+
staleTtlSeconds = resolved.envelope.staleUntil !== null ? Math.max(0, Math.ceil((resolved.envelope.staleUntil - now) / 1e3)) : null;
|
|
1644
|
+
errorTtlSeconds = resolved.envelope.errorUntil !== null ? Math.max(0, Math.ceil((resolved.envelope.errorUntil - now) / 1e3)) : null;
|
|
1645
|
+
isStale = resolved.state === "stale-while-revalidate" || resolved.state === "stale-if-error";
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
if (foundInLayers.length === 0) {
|
|
1649
|
+
return null;
|
|
1650
|
+
}
|
|
1651
|
+
const tags = await this.getTagsForKey(normalizedKey);
|
|
1652
|
+
return { key: userKey, foundInLayers, freshTtlSeconds, staleTtlSeconds, errorTtlSeconds, isStale, tags };
|
|
1653
|
+
}
|
|
1654
|
+
async exportState() {
|
|
1655
|
+
await this.awaitStartup("exportState");
|
|
1186
1656
|
const exported = /* @__PURE__ */ new Map();
|
|
1187
1657
|
for (const layer of this.layers) {
|
|
1188
1658
|
if (!layer.keys) {
|
|
@@ -1190,15 +1660,16 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1190
1660
|
}
|
|
1191
1661
|
const keys = await layer.keys();
|
|
1192
1662
|
for (const key of keys) {
|
|
1193
|
-
|
|
1663
|
+
const exportedKey = this.stripQualifiedKey(key);
|
|
1664
|
+
if (exported.has(exportedKey)) {
|
|
1194
1665
|
continue;
|
|
1195
1666
|
}
|
|
1196
1667
|
const stored = await this.readLayerEntry(layer, key);
|
|
1197
1668
|
if (stored === null) {
|
|
1198
1669
|
continue;
|
|
1199
1670
|
}
|
|
1200
|
-
exported.set(
|
|
1201
|
-
key,
|
|
1671
|
+
exported.set(exportedKey, {
|
|
1672
|
+
key: exportedKey,
|
|
1202
1673
|
value: stored,
|
|
1203
1674
|
ttl: remainingStoredTtlSeconds(stored)
|
|
1204
1675
|
});
|
|
@@ -1207,20 +1678,25 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1207
1678
|
return [...exported.values()];
|
|
1208
1679
|
}
|
|
1209
1680
|
async importState(entries) {
|
|
1210
|
-
await this.
|
|
1681
|
+
await this.awaitStartup("importState");
|
|
1211
1682
|
await Promise.all(
|
|
1212
1683
|
entries.map(async (entry) => {
|
|
1213
|
-
|
|
1214
|
-
await this.
|
|
1684
|
+
const qualifiedKey = this.qualifyKey(entry.key);
|
|
1685
|
+
await Promise.all(this.layers.map((layer) => layer.set(qualifiedKey, entry.value, entry.ttl)));
|
|
1686
|
+
await this.tagIndex.touch(qualifiedKey);
|
|
1215
1687
|
})
|
|
1216
1688
|
);
|
|
1217
1689
|
}
|
|
1218
1690
|
async persistToFile(filePath) {
|
|
1691
|
+
this.assertActive("persistToFile");
|
|
1219
1692
|
const snapshot = await this.exportState();
|
|
1220
|
-
|
|
1693
|
+
const { promises: fs } = await import("fs");
|
|
1694
|
+
await fs.writeFile(filePath, JSON.stringify(snapshot, null, 2), "utf8");
|
|
1221
1695
|
}
|
|
1222
1696
|
async restoreFromFile(filePath) {
|
|
1223
|
-
|
|
1697
|
+
this.assertActive("restoreFromFile");
|
|
1698
|
+
const { promises: fs } = await import("fs");
|
|
1699
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
1224
1700
|
let parsed;
|
|
1225
1701
|
try {
|
|
1226
1702
|
parsed = JSON.parse(raw, (_key, value) => {
|
|
@@ -1243,7 +1719,13 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1243
1719
|
this.disconnectPromise = (async () => {
|
|
1244
1720
|
await this.startup;
|
|
1245
1721
|
await this.unsubscribeInvalidation?.();
|
|
1722
|
+
await this.flushWriteBehindQueue();
|
|
1246
1723
|
await Promise.allSettled([...this.backgroundRefreshes.values()]);
|
|
1724
|
+
if (this.writeBehindTimer) {
|
|
1725
|
+
clearInterval(this.writeBehindTimer);
|
|
1726
|
+
this.writeBehindTimer = void 0;
|
|
1727
|
+
}
|
|
1728
|
+
await Promise.allSettled(this.layers.map((layer) => layer.dispose?.() ?? Promise.resolve()));
|
|
1247
1729
|
})();
|
|
1248
1730
|
}
|
|
1249
1731
|
await this.disconnectPromise;
|
|
@@ -1303,7 +1785,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1303
1785
|
const fetchStart = Date.now();
|
|
1304
1786
|
let fetched;
|
|
1305
1787
|
try {
|
|
1306
|
-
fetched = await
|
|
1788
|
+
fetched = await this.fetchRateLimiter.schedule(
|
|
1789
|
+
options?.fetcherRateLimit ?? this.options.fetcherRateLimit,
|
|
1790
|
+
fetcher
|
|
1791
|
+
);
|
|
1307
1792
|
this.circuitBreakerManager.recordSuccess(key);
|
|
1308
1793
|
this.logger.debug?.("fetch", { key, durationMs: Date.now() - fetchStart });
|
|
1309
1794
|
} catch (error) {
|
|
@@ -1317,6 +1802,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1317
1802
|
await this.storeEntry(key, "empty", null, options);
|
|
1318
1803
|
return null;
|
|
1319
1804
|
}
|
|
1805
|
+
if (options?.shouldCache && !options.shouldCache(fetched)) {
|
|
1806
|
+
return fetched;
|
|
1807
|
+
}
|
|
1320
1808
|
await this.storeEntry(key, "value", fetched, options);
|
|
1321
1809
|
return fetched;
|
|
1322
1810
|
}
|
|
@@ -1334,12 +1822,70 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1334
1822
|
await this.publishInvalidation({ scope: "key", keys: [key], sourceId: this.instanceId, operation: "write" });
|
|
1335
1823
|
}
|
|
1336
1824
|
}
|
|
1825
|
+
async writeBatch(entries) {
|
|
1826
|
+
const now = Date.now();
|
|
1827
|
+
const entriesByLayer = /* @__PURE__ */ new Map();
|
|
1828
|
+
const immediateOperations = [];
|
|
1829
|
+
const deferredOperations = [];
|
|
1830
|
+
for (const entry of entries) {
|
|
1831
|
+
for (const layer of this.layers) {
|
|
1832
|
+
if (this.shouldSkipLayer(layer)) {
|
|
1833
|
+
continue;
|
|
1834
|
+
}
|
|
1835
|
+
const layerEntry = this.buildLayerSetEntry(layer, entry.key, "value", entry.value, entry.options, now);
|
|
1836
|
+
const bucket = entriesByLayer.get(layer) ?? [];
|
|
1837
|
+
bucket.push(layerEntry);
|
|
1838
|
+
entriesByLayer.set(layer, bucket);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
for (const [layer, layerEntries] of entriesByLayer.entries()) {
|
|
1842
|
+
const operation = async () => {
|
|
1843
|
+
try {
|
|
1844
|
+
if (layer.setMany) {
|
|
1845
|
+
await layer.setMany(layerEntries);
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
await Promise.all(layerEntries.map((entry) => layer.set(entry.key, entry.value, entry.ttl)));
|
|
1849
|
+
} catch (error) {
|
|
1850
|
+
await this.handleLayerFailure(layer, "write", error);
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
if (this.shouldWriteBehind(layer)) {
|
|
1854
|
+
deferredOperations.push(operation);
|
|
1855
|
+
} else {
|
|
1856
|
+
immediateOperations.push(operation);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
await this.executeLayerOperations(immediateOperations, { key: "batch", action: "mset" });
|
|
1860
|
+
await Promise.all(deferredOperations.map((operation) => this.enqueueWriteBehind(operation)));
|
|
1861
|
+
for (const entry of entries) {
|
|
1862
|
+
if (entry.options?.tags) {
|
|
1863
|
+
await this.tagIndex.track(entry.key, entry.options.tags);
|
|
1864
|
+
} else {
|
|
1865
|
+
await this.tagIndex.touch(entry.key);
|
|
1866
|
+
}
|
|
1867
|
+
this.metricsCollector.increment("sets");
|
|
1868
|
+
this.logger.debug?.("set", { key: entry.key, kind: "value", tags: entry.options?.tags });
|
|
1869
|
+
this.emit("set", { key: entry.key, kind: "value", tags: entry.options?.tags });
|
|
1870
|
+
}
|
|
1871
|
+
if (this.shouldBroadcastL1Invalidation()) {
|
|
1872
|
+
await this.publishInvalidation({
|
|
1873
|
+
scope: "keys",
|
|
1874
|
+
keys: entries.map((entry) => entry.key),
|
|
1875
|
+
sourceId: this.instanceId,
|
|
1876
|
+
operation: "write"
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1337
1880
|
async readFromLayers(key, options, mode) {
|
|
1338
1881
|
let sawRetainableValue = false;
|
|
1339
1882
|
for (let index = 0; index < this.layers.length; index += 1) {
|
|
1340
1883
|
const layer = this.layers[index];
|
|
1341
1884
|
if (!layer) continue;
|
|
1885
|
+
const readStart = performance.now();
|
|
1342
1886
|
const stored = await this.readLayerEntry(layer, key);
|
|
1887
|
+
const readDuration = performance.now() - readStart;
|
|
1888
|
+
this.metricsCollector.recordLatency(layer.name, readDuration);
|
|
1343
1889
|
if (stored === null) {
|
|
1344
1890
|
this.metricsCollector.incrementLayer("missesByLayer", layer.name);
|
|
1345
1891
|
continue;
|
|
@@ -1414,33 +1960,28 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1414
1960
|
}
|
|
1415
1961
|
async writeAcrossLayers(key, kind, value, options) {
|
|
1416
1962
|
const now = Date.now();
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
options
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
});
|
|
1436
|
-
const ttl = remainingStoredTtlSeconds(payload, now) ?? freshTtl;
|
|
1437
|
-
try {
|
|
1438
|
-
await layer.set(key, payload, ttl);
|
|
1439
|
-
} catch (error) {
|
|
1440
|
-
await this.handleLayerFailure(layer, "write", error);
|
|
1963
|
+
const immediateOperations = [];
|
|
1964
|
+
const deferredOperations = [];
|
|
1965
|
+
for (const layer of this.layers) {
|
|
1966
|
+
const operation = async () => {
|
|
1967
|
+
if (this.shouldSkipLayer(layer)) {
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
const entry = this.buildLayerSetEntry(layer, key, kind, value, options, now);
|
|
1971
|
+
try {
|
|
1972
|
+
await layer.set(entry.key, entry.value, entry.ttl);
|
|
1973
|
+
} catch (error) {
|
|
1974
|
+
await this.handleLayerFailure(layer, "write", error);
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
if (this.shouldWriteBehind(layer)) {
|
|
1978
|
+
deferredOperations.push(operation);
|
|
1979
|
+
} else {
|
|
1980
|
+
immediateOperations.push(operation);
|
|
1441
1981
|
}
|
|
1442
|
-
}
|
|
1443
|
-
await this.executeLayerOperations(
|
|
1982
|
+
}
|
|
1983
|
+
await this.executeLayerOperations(immediateOperations, { key, action: kind === "empty" ? "negative-set" : "set" });
|
|
1984
|
+
await Promise.all(deferredOperations.map((operation) => this.enqueueWriteBehind(operation)));
|
|
1444
1985
|
}
|
|
1445
1986
|
async executeLayerOperations(operations, context) {
|
|
1446
1987
|
if (this.options.writePolicy !== "best-effort") {
|
|
@@ -1464,8 +2005,17 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1464
2005
|
);
|
|
1465
2006
|
}
|
|
1466
2007
|
}
|
|
1467
|
-
resolveFreshTtl(key, layerName, kind, options, fallbackTtl) {
|
|
1468
|
-
return this.ttlResolver.resolveFreshTtl(
|
|
2008
|
+
resolveFreshTtl(key, layerName, kind, options, fallbackTtl, value) {
|
|
2009
|
+
return this.ttlResolver.resolveFreshTtl(
|
|
2010
|
+
key,
|
|
2011
|
+
layerName,
|
|
2012
|
+
kind,
|
|
2013
|
+
options,
|
|
2014
|
+
fallbackTtl,
|
|
2015
|
+
this.options.negativeTtl,
|
|
2016
|
+
void 0,
|
|
2017
|
+
value
|
|
2018
|
+
);
|
|
1469
2019
|
}
|
|
1470
2020
|
resolveLayerSeconds(layerName, override, globalDefault, fallback) {
|
|
1471
2021
|
return this.ttlResolver.resolveLayerSeconds(layerName, override, globalDefault, fallback);
|
|
@@ -1541,6 +2091,12 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1541
2091
|
}
|
|
1542
2092
|
}
|
|
1543
2093
|
}
|
|
2094
|
+
async getTagsForKey(key) {
|
|
2095
|
+
if (this.tagIndex.tagsForKey) {
|
|
2096
|
+
return this.tagIndex.tagsForKey(key);
|
|
2097
|
+
}
|
|
2098
|
+
return [];
|
|
2099
|
+
}
|
|
1544
2100
|
formatError(error) {
|
|
1545
2101
|
if (error instanceof Error) {
|
|
1546
2102
|
return error.message;
|
|
@@ -1553,6 +2109,105 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1553
2109
|
shouldBroadcastL1Invalidation() {
|
|
1554
2110
|
return this.options.broadcastL1Invalidation ?? this.options.publishSetInvalidation ?? true;
|
|
1555
2111
|
}
|
|
2112
|
+
initializeWriteBehind(options) {
|
|
2113
|
+
if (this.options.writeStrategy !== "write-behind") {
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
const flushIntervalMs = options?.flushIntervalMs;
|
|
2117
|
+
if (!flushIntervalMs || flushIntervalMs <= 0) {
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
this.writeBehindTimer = setInterval(() => {
|
|
2121
|
+
void this.flushWriteBehindQueue();
|
|
2122
|
+
}, flushIntervalMs);
|
|
2123
|
+
this.writeBehindTimer.unref?.();
|
|
2124
|
+
}
|
|
2125
|
+
shouldWriteBehind(layer) {
|
|
2126
|
+
return this.options.writeStrategy === "write-behind" && !layer.isLocal;
|
|
2127
|
+
}
|
|
2128
|
+
async enqueueWriteBehind(operation) {
|
|
2129
|
+
this.writeBehindQueue.push(operation);
|
|
2130
|
+
const batchSize = this.options.writeBehind?.batchSize ?? 100;
|
|
2131
|
+
const maxQueueSize = this.options.writeBehind?.maxQueueSize ?? batchSize * 10;
|
|
2132
|
+
if (this.writeBehindQueue.length >= batchSize) {
|
|
2133
|
+
await this.flushWriteBehindQueue();
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
if (this.writeBehindQueue.length >= maxQueueSize) {
|
|
2137
|
+
await this.flushWriteBehindQueue();
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
async flushWriteBehindQueue() {
|
|
2141
|
+
if (this.writeBehindFlushPromise || this.writeBehindQueue.length === 0) {
|
|
2142
|
+
await this.writeBehindFlushPromise;
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
const batchSize = this.options.writeBehind?.batchSize ?? 100;
|
|
2146
|
+
const batch = this.writeBehindQueue.splice(0, batchSize);
|
|
2147
|
+
this.writeBehindFlushPromise = (async () => {
|
|
2148
|
+
await Promise.allSettled(batch.map((operation) => operation()));
|
|
2149
|
+
})();
|
|
2150
|
+
await this.writeBehindFlushPromise;
|
|
2151
|
+
this.writeBehindFlushPromise = void 0;
|
|
2152
|
+
if (this.writeBehindQueue.length > 0) {
|
|
2153
|
+
await this.flushWriteBehindQueue();
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
buildLayerSetEntry(layer, key, kind, value, options, now) {
|
|
2157
|
+
const freshTtl = this.resolveFreshTtl(key, layer.name, kind, options, layer.defaultTtl, value);
|
|
2158
|
+
const staleWhileRevalidate = this.resolveLayerSeconds(
|
|
2159
|
+
layer.name,
|
|
2160
|
+
options?.staleWhileRevalidate,
|
|
2161
|
+
this.options.staleWhileRevalidate
|
|
2162
|
+
);
|
|
2163
|
+
const staleIfError = this.resolveLayerSeconds(layer.name, options?.staleIfError, this.options.staleIfError);
|
|
2164
|
+
const payload = createStoredValueEnvelope({
|
|
2165
|
+
kind,
|
|
2166
|
+
value,
|
|
2167
|
+
freshTtlSeconds: freshTtl,
|
|
2168
|
+
staleWhileRevalidateSeconds: staleWhileRevalidate,
|
|
2169
|
+
staleIfErrorSeconds: staleIfError,
|
|
2170
|
+
now
|
|
2171
|
+
});
|
|
2172
|
+
const ttl = remainingStoredTtlSeconds(payload, now) ?? freshTtl;
|
|
2173
|
+
return {
|
|
2174
|
+
key,
|
|
2175
|
+
value: payload,
|
|
2176
|
+
ttl
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
intersectKeys(groups) {
|
|
2180
|
+
if (groups.length === 0) {
|
|
2181
|
+
return [];
|
|
2182
|
+
}
|
|
2183
|
+
const [firstGroup, ...rest] = groups;
|
|
2184
|
+
if (!firstGroup) {
|
|
2185
|
+
return [];
|
|
2186
|
+
}
|
|
2187
|
+
const restSets = rest.map((group) => new Set(group));
|
|
2188
|
+
return [...new Set(firstGroup)].filter((key) => restSets.every((group) => group.has(key)));
|
|
2189
|
+
}
|
|
2190
|
+
qualifyKey(key) {
|
|
2191
|
+
const prefix = this.generationPrefix();
|
|
2192
|
+
return prefix ? `${prefix}${key}` : key;
|
|
2193
|
+
}
|
|
2194
|
+
qualifyPattern(pattern) {
|
|
2195
|
+
const prefix = this.generationPrefix();
|
|
2196
|
+
return prefix ? `${prefix}${pattern}` : pattern;
|
|
2197
|
+
}
|
|
2198
|
+
stripQualifiedKey(key) {
|
|
2199
|
+
const prefix = this.generationPrefix();
|
|
2200
|
+
if (!prefix || !key.startsWith(prefix)) {
|
|
2201
|
+
return key;
|
|
2202
|
+
}
|
|
2203
|
+
return key.slice(prefix.length);
|
|
2204
|
+
}
|
|
2205
|
+
generationPrefix() {
|
|
2206
|
+
if (this.currentGeneration === void 0) {
|
|
2207
|
+
return "";
|
|
2208
|
+
}
|
|
2209
|
+
return `v${this.currentGeneration}:`;
|
|
2210
|
+
}
|
|
1556
2211
|
async deleteKeysFromLayers(layers, keys) {
|
|
1557
2212
|
await Promise.all(
|
|
1558
2213
|
layers.map(async (layer) => {
|
|
@@ -1596,6 +2251,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1596
2251
|
this.validatePositiveNumber("singleFlightPollMs", this.options.singleFlightPollMs);
|
|
1597
2252
|
this.validateAdaptiveTtlOptions(this.options.adaptiveTtl);
|
|
1598
2253
|
this.validateCircuitBreakerOptions(this.options.circuitBreaker);
|
|
2254
|
+
if (this.options.generation !== void 0) {
|
|
2255
|
+
this.validateNonNegativeNumber("generation", this.options.generation);
|
|
2256
|
+
}
|
|
1599
2257
|
}
|
|
1600
2258
|
validateWriteOptions(options) {
|
|
1601
2259
|
if (!options) {
|
|
@@ -1607,6 +2265,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1607
2265
|
this.validateLayerNumberOption("options.staleIfError", options.staleIfError);
|
|
1608
2266
|
this.validateLayerNumberOption("options.ttlJitter", options.ttlJitter);
|
|
1609
2267
|
this.validateLayerNumberOption("options.refreshAhead", options.refreshAhead);
|
|
2268
|
+
this.validateTtlPolicy("options.ttlPolicy", options.ttlPolicy);
|
|
1610
2269
|
this.validateAdaptiveTtlOptions(options.adaptiveTtl);
|
|
1611
2270
|
this.validateCircuitBreakerOptions(options.circuitBreaker);
|
|
1612
2271
|
}
|
|
@@ -1650,6 +2309,26 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1650
2309
|
}
|
|
1651
2310
|
return key;
|
|
1652
2311
|
}
|
|
2312
|
+
validateTtlPolicy(name, policy) {
|
|
2313
|
+
if (!policy || typeof policy === "function" || policy === "until-midnight" || policy === "next-hour") {
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
if ("alignTo" in policy) {
|
|
2317
|
+
this.validatePositiveNumber(`${name}.alignTo`, policy.alignTo);
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
throw new Error(`${name} is invalid.`);
|
|
2321
|
+
}
|
|
2322
|
+
assertActive(operation) {
|
|
2323
|
+
if (this.isDisconnecting) {
|
|
2324
|
+
throw new Error(`CacheStack is disconnecting; cannot perform ${operation}.`);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
async awaitStartup(operation) {
|
|
2328
|
+
this.assertActive(operation);
|
|
2329
|
+
await this.startup;
|
|
2330
|
+
this.assertActive(operation);
|
|
2331
|
+
}
|
|
1653
2332
|
serializeOptions(options) {
|
|
1654
2333
|
return JSON.stringify(this.normalizeForSerialization(options) ?? null);
|
|
1655
2334
|
}
|
|
@@ -1755,6 +2434,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1755
2434
|
return value;
|
|
1756
2435
|
}
|
|
1757
2436
|
};
|
|
2437
|
+
function createInstanceId() {
|
|
2438
|
+
return globalThis.crypto?.randomUUID?.() ?? `layercache-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2439
|
+
}
|
|
1758
2440
|
|
|
1759
2441
|
// src/module.ts
|
|
1760
2442
|
var InjectCacheStack = () => (0, import_common.Inject)(CACHE_STACK);
|
|
@@ -1771,6 +2453,22 @@ var CacheStackModule = class {
|
|
|
1771
2453
|
exports: [provider]
|
|
1772
2454
|
};
|
|
1773
2455
|
}
|
|
2456
|
+
static forRootAsync(options) {
|
|
2457
|
+
const provider = {
|
|
2458
|
+
provide: CACHE_STACK,
|
|
2459
|
+
inject: options.inject ?? [],
|
|
2460
|
+
useFactory: async (...args) => {
|
|
2461
|
+
const resolved = await options.useFactory(...args);
|
|
2462
|
+
return new CacheStack(resolved.layers, resolved.bridgeOptions);
|
|
2463
|
+
}
|
|
2464
|
+
};
|
|
2465
|
+
return {
|
|
2466
|
+
global: true,
|
|
2467
|
+
module: CacheStackModule,
|
|
2468
|
+
providers: [provider],
|
|
2469
|
+
exports: [provider]
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
1774
2472
|
};
|
|
1775
2473
|
CacheStackModule = __decorateClass([
|
|
1776
2474
|
(0, import_common.Global)(),
|