layercache 1.2.0 → 1.2.2
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 +110 -8
- package/dist/chunk-46UH7LNM.js +312 -0
- package/dist/{chunk-BWM4MU2X.js → chunk-IXCMHVHP.js} +62 -56
- package/dist/chunk-ZMDB5KOK.js +159 -0
- package/dist/cli.cjs +170 -39
- package/dist/cli.js +57 -2
- package/dist/edge-DLpdQN0W.d.cts +672 -0
- package/dist/edge-DLpdQN0W.d.ts +672 -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 +1173 -221
- package/dist/index.d.cts +51 -568
- package/dist/index.d.ts +51 -568
- package/dist/index.js +1005 -505
- package/package.json +8 -3
- package/packages/nestjs/dist/index.cjs +980 -370
- package/packages/nestjs/dist/index.d.cts +80 -0
- package/packages/nestjs/dist/index.d.ts +80 -0
- package/packages/nestjs/dist/index.js +968 -368
|
@@ -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,9 +82,182 @@ 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
263
|
var CacheNamespace = class _CacheNamespace {
|
|
@@ -84,57 +267,69 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
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));
|
|
92
277
|
}
|
|
93
278
|
/**
|
|
94
279
|
* Like `get()`, but throws `CacheMissError` instead of returning `null`.
|
|
95
280
|
*/
|
|
96
281
|
async getOrThrow(key, fetcher, options) {
|
|
97
|
-
return this.cache.getOrThrow(this.qualify(key), fetcher, options);
|
|
282
|
+
return this.trackMetrics(() => this.cache.getOrThrow(this.qualify(key), fetcher, options));
|
|
98
283
|
}
|
|
99
284
|
async has(key) {
|
|
100
|
-
return this.cache.has(this.qualify(key));
|
|
285
|
+
return this.trackMetrics(() => this.cache.has(this.qualify(key)));
|
|
101
286
|
}
|
|
102
287
|
async ttl(key) {
|
|
103
|
-
return this.cache.ttl(this.qualify(key));
|
|
288
|
+
return this.trackMetrics(() => this.cache.ttl(this.qualify(key)));
|
|
104
289
|
}
|
|
105
290
|
async set(key, value, options) {
|
|
106
|
-
await this.cache.set(this.qualify(key), value, options);
|
|
291
|
+
await this.trackMetrics(() => this.cache.set(this.qualify(key), value, options));
|
|
107
292
|
}
|
|
108
293
|
async delete(key) {
|
|
109
|
-
await this.cache.delete(this.qualify(key));
|
|
294
|
+
await this.trackMetrics(() => this.cache.delete(this.qualify(key)));
|
|
110
295
|
}
|
|
111
296
|
async mdelete(keys) {
|
|
112
|
-
await this.cache.mdelete(keys.map((k) => this.qualify(k)));
|
|
297
|
+
await this.trackMetrics(() => this.cache.mdelete(keys.map((k) => this.qualify(k))));
|
|
113
298
|
}
|
|
114
299
|
async clear() {
|
|
115
|
-
await this.cache.
|
|
300
|
+
await this.trackMetrics(() => this.cache.invalidateByPrefix(this.prefix));
|
|
116
301
|
}
|
|
117
302
|
async mget(entries) {
|
|
118
|
-
return this.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
303
|
+
return this.trackMetrics(
|
|
304
|
+
() => this.cache.mget(
|
|
305
|
+
entries.map((entry) => ({
|
|
306
|
+
...entry,
|
|
307
|
+
key: this.qualify(entry.key)
|
|
308
|
+
}))
|
|
309
|
+
)
|
|
123
310
|
);
|
|
124
311
|
}
|
|
125
312
|
async mset(entries) {
|
|
126
|
-
await this.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
313
|
+
await this.trackMetrics(
|
|
314
|
+
() => this.cache.mset(
|
|
315
|
+
entries.map((entry) => ({
|
|
316
|
+
...entry,
|
|
317
|
+
key: this.qualify(entry.key)
|
|
318
|
+
}))
|
|
319
|
+
)
|
|
131
320
|
);
|
|
132
321
|
}
|
|
133
322
|
async invalidateByTag(tag) {
|
|
134
|
-
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));
|
|
135
327
|
}
|
|
136
328
|
async invalidateByPattern(pattern) {
|
|
137
|
-
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)));
|
|
138
333
|
}
|
|
139
334
|
/**
|
|
140
335
|
* Returns detailed metadata about a single cache key within this namespace.
|
|
@@ -155,10 +350,19 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
155
350
|
);
|
|
156
351
|
}
|
|
157
352
|
getMetrics() {
|
|
158
|
-
return this.
|
|
353
|
+
return cloneMetrics(this.metrics);
|
|
159
354
|
}
|
|
160
355
|
getHitRate() {
|
|
161
|
-
|
|
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 };
|
|
162
366
|
}
|
|
163
367
|
/**
|
|
164
368
|
* Creates a nested namespace. Keys are prefixed with `parentPrefix:childPrefix:`.
|
|
@@ -175,7 +379,130 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
175
379
|
qualify(key) {
|
|
176
380
|
return `${this.prefix}:${key}`;
|
|
177
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
|
+
}
|
|
178
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
|
+
}
|
|
179
506
|
|
|
180
507
|
// ../../src/internal/CircuitBreakerManager.ts
|
|
181
508
|
var CircuitBreakerManager = class {
|
|
@@ -269,6 +596,148 @@ var CircuitBreakerManager = class {
|
|
|
269
596
|
}
|
|
270
597
|
};
|
|
271
598
|
|
|
599
|
+
// ../../src/internal/FetchRateLimiter.ts
|
|
600
|
+
var FetchRateLimiter = class {
|
|
601
|
+
queue = [];
|
|
602
|
+
buckets = /* @__PURE__ */ new Map();
|
|
603
|
+
fetcherBuckets = /* @__PURE__ */ new WeakMap();
|
|
604
|
+
nextFetcherBucketId = 0;
|
|
605
|
+
drainTimer;
|
|
606
|
+
async schedule(options, context, task) {
|
|
607
|
+
if (!options) {
|
|
608
|
+
return task();
|
|
609
|
+
}
|
|
610
|
+
const normalized = this.normalize(options);
|
|
611
|
+
if (!normalized) {
|
|
612
|
+
return task();
|
|
613
|
+
}
|
|
614
|
+
return new Promise((resolve, reject) => {
|
|
615
|
+
this.queue.push({
|
|
616
|
+
bucketKey: this.resolveBucketKey(normalized, context),
|
|
617
|
+
options: normalized,
|
|
618
|
+
task,
|
|
619
|
+
resolve,
|
|
620
|
+
reject
|
|
621
|
+
});
|
|
622
|
+
this.drain();
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
normalize(options) {
|
|
626
|
+
const maxConcurrent = options.maxConcurrent;
|
|
627
|
+
const intervalMs = options.intervalMs;
|
|
628
|
+
const maxPerInterval = options.maxPerInterval;
|
|
629
|
+
if (!maxConcurrent && !(intervalMs && maxPerInterval)) {
|
|
630
|
+
return void 0;
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
maxConcurrent,
|
|
634
|
+
intervalMs,
|
|
635
|
+
maxPerInterval,
|
|
636
|
+
scope: options.scope ?? "global",
|
|
637
|
+
bucketKey: options.bucketKey
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
resolveBucketKey(options, context) {
|
|
641
|
+
if (options.bucketKey) {
|
|
642
|
+
return `custom:${options.bucketKey}`;
|
|
643
|
+
}
|
|
644
|
+
if (options.scope === "key") {
|
|
645
|
+
return `key:${context.key}`;
|
|
646
|
+
}
|
|
647
|
+
if (options.scope === "fetcher") {
|
|
648
|
+
const existing = this.fetcherBuckets.get(context.fetcher);
|
|
649
|
+
if (existing) {
|
|
650
|
+
return existing;
|
|
651
|
+
}
|
|
652
|
+
const bucket = `fetcher:${this.nextFetcherBucketId}`;
|
|
653
|
+
this.nextFetcherBucketId += 1;
|
|
654
|
+
this.fetcherBuckets.set(context.fetcher, bucket);
|
|
655
|
+
return bucket;
|
|
656
|
+
}
|
|
657
|
+
return "global";
|
|
658
|
+
}
|
|
659
|
+
drain() {
|
|
660
|
+
if (this.drainTimer) {
|
|
661
|
+
clearTimeout(this.drainTimer);
|
|
662
|
+
this.drainTimer = void 0;
|
|
663
|
+
}
|
|
664
|
+
while (this.queue.length > 0) {
|
|
665
|
+
let nextIndex = -1;
|
|
666
|
+
let nextWaitMs = Number.POSITIVE_INFINITY;
|
|
667
|
+
for (let index = 0; index < this.queue.length; index += 1) {
|
|
668
|
+
const next2 = this.queue[index];
|
|
669
|
+
if (!next2) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const waitMs = this.waitTime(next2.bucketKey, next2.options);
|
|
673
|
+
if (waitMs <= 0) {
|
|
674
|
+
nextIndex = index;
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
nextWaitMs = Math.min(nextWaitMs, waitMs);
|
|
678
|
+
}
|
|
679
|
+
if (nextIndex < 0) {
|
|
680
|
+
if (Number.isFinite(nextWaitMs)) {
|
|
681
|
+
this.drainTimer = setTimeout(() => {
|
|
682
|
+
this.drainTimer = void 0;
|
|
683
|
+
this.drain();
|
|
684
|
+
}, nextWaitMs);
|
|
685
|
+
this.drainTimer.unref?.();
|
|
686
|
+
}
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const next = this.queue.splice(nextIndex, 1)[0];
|
|
690
|
+
if (!next) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const bucket = this.bucketState(next.bucketKey);
|
|
694
|
+
bucket.active += 1;
|
|
695
|
+
bucket.startedAt.push(Date.now());
|
|
696
|
+
void next.task().then(next.resolve, next.reject).finally(() => {
|
|
697
|
+
bucket.active -= 1;
|
|
698
|
+
this.drain();
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
waitTime(bucketKey, options) {
|
|
703
|
+
const bucket = this.bucketState(bucketKey);
|
|
704
|
+
const now = Date.now();
|
|
705
|
+
if (options.maxConcurrent && bucket.active >= options.maxConcurrent) {
|
|
706
|
+
return 1;
|
|
707
|
+
}
|
|
708
|
+
if (!options.intervalMs || !options.maxPerInterval) {
|
|
709
|
+
return 0;
|
|
710
|
+
}
|
|
711
|
+
this.prune(bucket, now, options.intervalMs);
|
|
712
|
+
if (bucket.startedAt.length < options.maxPerInterval) {
|
|
713
|
+
return 0;
|
|
714
|
+
}
|
|
715
|
+
const oldest = bucket.startedAt[0];
|
|
716
|
+
if (!oldest) {
|
|
717
|
+
return 0;
|
|
718
|
+
}
|
|
719
|
+
return Math.max(1, options.intervalMs - (now - oldest));
|
|
720
|
+
}
|
|
721
|
+
prune(bucket, now, intervalMs) {
|
|
722
|
+
while (bucket.startedAt.length > 0) {
|
|
723
|
+
const startedAt = bucket.startedAt[0];
|
|
724
|
+
if (startedAt === void 0 || now - startedAt < intervalMs) {
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
bucket.startedAt.shift();
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
bucketState(bucketKey) {
|
|
731
|
+
const existing = this.buckets.get(bucketKey);
|
|
732
|
+
if (existing) {
|
|
733
|
+
return existing;
|
|
734
|
+
}
|
|
735
|
+
const bucket = { active: 0, startedAt: [] };
|
|
736
|
+
this.buckets.set(bucketKey, bucket);
|
|
737
|
+
return bucket;
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
|
|
272
741
|
// ../../src/internal/MetricsCollector.ts
|
|
273
742
|
var MetricsCollector = class {
|
|
274
743
|
data = this.empty();
|
|
@@ -465,13 +934,14 @@ var TtlResolver = class {
|
|
|
465
934
|
clearProfiles() {
|
|
466
935
|
this.accessProfiles.clear();
|
|
467
936
|
}
|
|
468
|
-
resolveFreshTtl(key, layerName, kind, options, fallbackTtl, globalNegativeTtl, globalTtl) {
|
|
937
|
+
resolveFreshTtl(key, layerName, kind, options, fallbackTtl, globalNegativeTtl, globalTtl, value) {
|
|
938
|
+
const policyTtl = kind === "value" ? this.resolvePolicyTtl(key, value, options?.ttlPolicy) : void 0;
|
|
469
939
|
const baseTtl = kind === "empty" ? this.resolveLayerSeconds(
|
|
470
940
|
layerName,
|
|
471
941
|
options?.negativeTtl,
|
|
472
942
|
globalNegativeTtl,
|
|
473
|
-
this.resolveLayerSeconds(layerName, options?.ttl, globalTtl, fallbackTtl) ?? DEFAULT_NEGATIVE_TTL_SECONDS
|
|
474
|
-
) : this.resolveLayerSeconds(layerName, options?.ttl, globalTtl, fallbackTtl);
|
|
943
|
+
this.resolveLayerSeconds(layerName, options?.ttl, globalTtl, policyTtl ?? fallbackTtl) ?? DEFAULT_NEGATIVE_TTL_SECONDS
|
|
944
|
+
) : this.resolveLayerSeconds(layerName, options?.ttl, globalTtl, policyTtl ?? fallbackTtl);
|
|
475
945
|
const adaptiveTtl = this.applyAdaptiveTtl(key, layerName, baseTtl, options?.adaptiveTtl);
|
|
476
946
|
const jitter = this.resolveLayerSeconds(layerName, options?.ttlJitter, void 0);
|
|
477
947
|
return this.applyJitter(adaptiveTtl, jitter);
|
|
@@ -510,6 +980,29 @@ var TtlResolver = class {
|
|
|
510
980
|
const delta = (Math.random() * 2 - 1) * jitter;
|
|
511
981
|
return Math.max(1, Math.round(ttl + delta));
|
|
512
982
|
}
|
|
983
|
+
resolvePolicyTtl(key, value, policy) {
|
|
984
|
+
if (!policy) {
|
|
985
|
+
return void 0;
|
|
986
|
+
}
|
|
987
|
+
if (typeof policy === "function") {
|
|
988
|
+
return policy({ key, value });
|
|
989
|
+
}
|
|
990
|
+
const now = /* @__PURE__ */ new Date();
|
|
991
|
+
if (policy === "until-midnight") {
|
|
992
|
+
const nextMidnight = new Date(now);
|
|
993
|
+
nextMidnight.setHours(24, 0, 0, 0);
|
|
994
|
+
return Math.max(1, Math.ceil((nextMidnight.getTime() - now.getTime()) / 1e3));
|
|
995
|
+
}
|
|
996
|
+
if (policy === "next-hour") {
|
|
997
|
+
const nextHour = new Date(now);
|
|
998
|
+
nextHour.setMinutes(60, 0, 0);
|
|
999
|
+
return Math.max(1, Math.ceil((nextHour.getTime() - now.getTime()) / 1e3));
|
|
1000
|
+
}
|
|
1001
|
+
const alignToSeconds = policy.alignTo;
|
|
1002
|
+
const currentSeconds = Math.floor(Date.now() / 1e3);
|
|
1003
|
+
const nextBoundary = Math.ceil((currentSeconds + 1) / alignToSeconds) * alignToSeconds;
|
|
1004
|
+
return Math.max(1, nextBoundary - currentSeconds);
|
|
1005
|
+
}
|
|
513
1006
|
readLayerNumber(layerName, value) {
|
|
514
1007
|
if (typeof value === "number") {
|
|
515
1008
|
return value;
|
|
@@ -530,300 +1023,140 @@ var TtlResolver = class {
|
|
|
530
1023
|
removed += 1;
|
|
531
1024
|
}
|
|
532
1025
|
}
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
// ../../src/invalidation/PatternMatcher.ts
|
|
536
|
-
var PatternMatcher = class _PatternMatcher {
|
|
537
|
-
/**
|
|
538
|
-
* Tests whether a glob-style pattern matches a value.
|
|
539
|
-
* Supports `*` (any sequence of characters) and `?` (any single character).
|
|
540
|
-
* Uses a
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* Linear-time glob matching using dynamic programming.
|
|
547
|
-
* Avoids catastrophic backtracking that RegExp-based glob matching can cause.
|
|
548
|
-
*/
|
|
549
|
-
static matchLinear(pattern, value) {
|
|
550
|
-
const m = pattern.length;
|
|
551
|
-
const n = value.length;
|
|
552
|
-
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(false));
|
|
553
|
-
dp[0][0] = true;
|
|
554
|
-
for (let i = 1; i <= m; i++) {
|
|
555
|
-
if (pattern[i - 1] === "*") {
|
|
556
|
-
dp[i][0] = dp[i - 1]?.[0];
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
for (let i = 1; i <= m; i++) {
|
|
560
|
-
for (let j = 1; j <= n; j++) {
|
|
561
|
-
const pc = pattern[i - 1];
|
|
562
|
-
if (pc === "*") {
|
|
563
|
-
dp[i][j] = dp[i - 1]?.[j] || dp[i]?.[j - 1];
|
|
564
|
-
} else if (pc === "?" || pc === value[j - 1]) {
|
|
565
|
-
dp[i][j] = dp[i - 1]?.[j - 1];
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
return dp[m]?.[n];
|
|
570
|
-
}
|
|
571
|
-
};
|
|
572
|
-
|
|
573
|
-
// ../../src/invalidation/TagIndex.ts
|
|
574
|
-
var TagIndex = class {
|
|
575
|
-
tagToKeys = /* @__PURE__ */ new Map();
|
|
576
|
-
keyToTags = /* @__PURE__ */ new Map();
|
|
577
|
-
knownKeys = /* @__PURE__ */ new Set();
|
|
578
|
-
maxKnownKeys;
|
|
579
|
-
constructor(options = {}) {
|
|
580
|
-
this.maxKnownKeys = options.maxKnownKeys;
|
|
581
|
-
}
|
|
582
|
-
async touch(key) {
|
|
583
|
-
this.knownKeys.add(key);
|
|
584
|
-
this.pruneKnownKeysIfNeeded();
|
|
585
|
-
}
|
|
586
|
-
async track(key, tags) {
|
|
587
|
-
this.knownKeys.add(key);
|
|
588
|
-
this.pruneKnownKeysIfNeeded();
|
|
589
|
-
if (tags.length === 0) {
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
const existingTags = this.keyToTags.get(key);
|
|
593
|
-
if (existingTags) {
|
|
594
|
-
for (const tag of existingTags) {
|
|
595
|
-
this.tagToKeys.get(tag)?.delete(key);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
const tagSet = new Set(tags);
|
|
599
|
-
this.keyToTags.set(key, tagSet);
|
|
600
|
-
for (const tag of tagSet) {
|
|
601
|
-
const keys = this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set();
|
|
602
|
-
keys.add(key);
|
|
603
|
-
this.tagToKeys.set(tag, keys);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
async remove(key) {
|
|
607
|
-
this.knownKeys.delete(key);
|
|
608
|
-
const tags = this.keyToTags.get(key);
|
|
609
|
-
if (!tags) {
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
for (const tag of tags) {
|
|
613
|
-
const keys = this.tagToKeys.get(tag);
|
|
614
|
-
if (!keys) {
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
keys.delete(key);
|
|
618
|
-
if (keys.size === 0) {
|
|
619
|
-
this.tagToKeys.delete(tag);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
this.keyToTags.delete(key);
|
|
623
|
-
}
|
|
624
|
-
async keysForTag(tag) {
|
|
625
|
-
return [...this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()];
|
|
626
|
-
}
|
|
627
|
-
async tagsForKey(key) {
|
|
628
|
-
return [...this.keyToTags.get(key) ?? /* @__PURE__ */ new Set()];
|
|
629
|
-
}
|
|
630
|
-
async matchPattern(pattern) {
|
|
631
|
-
return [...this.knownKeys].filter((key) => PatternMatcher.matches(pattern, key));
|
|
632
|
-
}
|
|
633
|
-
async clear() {
|
|
634
|
-
this.tagToKeys.clear();
|
|
635
|
-
this.keyToTags.clear();
|
|
636
|
-
this.knownKeys.clear();
|
|
637
|
-
}
|
|
638
|
-
pruneKnownKeysIfNeeded() {
|
|
639
|
-
if (this.maxKnownKeys === void 0 || this.knownKeys.size <= this.maxKnownKeys) {
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
const toRemove = Math.ceil(this.maxKnownKeys * 0.1);
|
|
643
|
-
let removed = 0;
|
|
644
|
-
for (const key of this.knownKeys) {
|
|
645
|
-
if (removed >= toRemove) {
|
|
646
|
-
break;
|
|
647
|
-
}
|
|
648
|
-
this.knownKeys.delete(key);
|
|
649
|
-
this.keyToTags.delete(key);
|
|
650
|
-
removed += 1;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
};
|
|
654
|
-
|
|
655
|
-
// ../../node_modules/async-mutex/index.mjs
|
|
656
|
-
var E_TIMEOUT = new Error("timeout while waiting for mutex to become available");
|
|
657
|
-
var E_ALREADY_LOCKED = new Error("mutex already locked");
|
|
658
|
-
var E_CANCELED = new Error("request for lock canceled");
|
|
659
|
-
var __awaiter$2 = function(thisArg, _arguments, P, generator) {
|
|
660
|
-
function adopt(value) {
|
|
661
|
-
return value instanceof P ? value : new P(function(resolve) {
|
|
662
|
-
resolve(value);
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
return new (P || (P = Promise))(function(resolve, reject) {
|
|
666
|
-
function fulfilled(value) {
|
|
667
|
-
try {
|
|
668
|
-
step(generator.next(value));
|
|
669
|
-
} catch (e) {
|
|
670
|
-
reject(e);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
function rejected(value) {
|
|
674
|
-
try {
|
|
675
|
-
step(generator["throw"](value));
|
|
676
|
-
} catch (e) {
|
|
677
|
-
reject(e);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
function step(result) {
|
|
681
|
-
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
682
|
-
}
|
|
683
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
684
|
-
});
|
|
685
|
-
};
|
|
686
|
-
var Semaphore = class {
|
|
687
|
-
constructor(_value, _cancelError = E_CANCELED) {
|
|
688
|
-
this._value = _value;
|
|
689
|
-
this._cancelError = _cancelError;
|
|
690
|
-
this._weightedQueues = [];
|
|
691
|
-
this._weightedWaiters = [];
|
|
692
|
-
}
|
|
693
|
-
acquire(weight = 1) {
|
|
694
|
-
if (weight <= 0)
|
|
695
|
-
throw new Error(`invalid weight ${weight}: must be positive`);
|
|
696
|
-
return new Promise((resolve, reject) => {
|
|
697
|
-
if (!this._weightedQueues[weight - 1])
|
|
698
|
-
this._weightedQueues[weight - 1] = [];
|
|
699
|
-
this._weightedQueues[weight - 1].push({ resolve, reject });
|
|
700
|
-
this._dispatch();
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
runExclusive(callback, weight = 1) {
|
|
704
|
-
return __awaiter$2(this, void 0, void 0, function* () {
|
|
705
|
-
const [value, release] = yield this.acquire(weight);
|
|
706
|
-
try {
|
|
707
|
-
return yield callback(value);
|
|
708
|
-
} finally {
|
|
709
|
-
release();
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
waitForUnlock(weight = 1) {
|
|
714
|
-
if (weight <= 0)
|
|
715
|
-
throw new Error(`invalid weight ${weight}: must be positive`);
|
|
716
|
-
return new Promise((resolve) => {
|
|
717
|
-
if (!this._weightedWaiters[weight - 1])
|
|
718
|
-
this._weightedWaiters[weight - 1] = [];
|
|
719
|
-
this._weightedWaiters[weight - 1].push(resolve);
|
|
720
|
-
this._dispatch();
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
isLocked() {
|
|
724
|
-
return this._value <= 0;
|
|
725
|
-
}
|
|
726
|
-
getValue() {
|
|
727
|
-
return this._value;
|
|
728
|
-
}
|
|
729
|
-
setValue(value) {
|
|
730
|
-
this._value = value;
|
|
731
|
-
this._dispatch();
|
|
732
|
-
}
|
|
733
|
-
release(weight = 1) {
|
|
734
|
-
if (weight <= 0)
|
|
735
|
-
throw new Error(`invalid weight ${weight}: must be positive`);
|
|
736
|
-
this._value += weight;
|
|
737
|
-
this._dispatch();
|
|
738
|
-
}
|
|
739
|
-
cancel() {
|
|
740
|
-
this._weightedQueues.forEach((queue) => queue.forEach((entry) => entry.reject(this._cancelError)));
|
|
741
|
-
this._weightedQueues = [];
|
|
742
|
-
}
|
|
743
|
-
_dispatch() {
|
|
744
|
-
var _a;
|
|
745
|
-
for (let weight = this._value; weight > 0; weight--) {
|
|
746
|
-
const queueEntry = (_a = this._weightedQueues[weight - 1]) === null || _a === void 0 ? void 0 : _a.shift();
|
|
747
|
-
if (!queueEntry)
|
|
748
|
-
continue;
|
|
749
|
-
const previousValue = this._value;
|
|
750
|
-
const previousWeight = weight;
|
|
751
|
-
this._value -= weight;
|
|
752
|
-
weight = this._value + 1;
|
|
753
|
-
queueEntry.resolve([previousValue, this._newReleaser(previousWeight)]);
|
|
754
|
-
}
|
|
755
|
-
this._drainUnlockWaiters();
|
|
756
|
-
}
|
|
757
|
-
_newReleaser(weight) {
|
|
758
|
-
let called = false;
|
|
759
|
-
return () => {
|
|
760
|
-
if (called)
|
|
761
|
-
return;
|
|
762
|
-
called = true;
|
|
763
|
-
this.release(weight);
|
|
764
|
-
};
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
// ../../src/invalidation/PatternMatcher.ts
|
|
1029
|
+
var PatternMatcher = class _PatternMatcher {
|
|
1030
|
+
/**
|
|
1031
|
+
* Tests whether a glob-style pattern matches a value.
|
|
1032
|
+
* Supports `*` (any sequence of characters) and `?` (any single character).
|
|
1033
|
+
* Uses a two-pointer algorithm to avoid ReDoS vulnerabilities and
|
|
1034
|
+
* quadratic memory usage on long patterns/keys.
|
|
1035
|
+
*/
|
|
1036
|
+
static matches(pattern, value) {
|
|
1037
|
+
return _PatternMatcher.matchLinear(pattern, value);
|
|
765
1038
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1039
|
+
/**
|
|
1040
|
+
* Linear-time glob matching with O(1) extra memory.
|
|
1041
|
+
*/
|
|
1042
|
+
static matchLinear(pattern, value) {
|
|
1043
|
+
let patternIndex = 0;
|
|
1044
|
+
let valueIndex = 0;
|
|
1045
|
+
let starIndex = -1;
|
|
1046
|
+
let backtrackValueIndex = 0;
|
|
1047
|
+
while (valueIndex < value.length) {
|
|
1048
|
+
const patternChar = pattern[patternIndex];
|
|
1049
|
+
const valueChar = value[valueIndex];
|
|
1050
|
+
if (patternChar === "*" && patternIndex < pattern.length) {
|
|
1051
|
+
starIndex = patternIndex;
|
|
1052
|
+
patternIndex += 1;
|
|
1053
|
+
backtrackValueIndex = valueIndex;
|
|
769
1054
|
continue;
|
|
770
|
-
|
|
771
|
-
|
|
1055
|
+
}
|
|
1056
|
+
if (patternChar === "?" || patternChar === valueChar) {
|
|
1057
|
+
patternIndex += 1;
|
|
1058
|
+
valueIndex += 1;
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (starIndex !== -1) {
|
|
1062
|
+
patternIndex = starIndex + 1;
|
|
1063
|
+
backtrackValueIndex += 1;
|
|
1064
|
+
valueIndex = backtrackValueIndex;
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
return false;
|
|
772
1068
|
}
|
|
1069
|
+
while (patternIndex < pattern.length && pattern[patternIndex] === "*") {
|
|
1070
|
+
patternIndex += 1;
|
|
1071
|
+
}
|
|
1072
|
+
return patternIndex === pattern.length;
|
|
773
1073
|
}
|
|
774
1074
|
};
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1075
|
+
|
|
1076
|
+
// ../../src/invalidation/TagIndex.ts
|
|
1077
|
+
var TagIndex = class {
|
|
1078
|
+
tagToKeys = /* @__PURE__ */ new Map();
|
|
1079
|
+
keyToTags = /* @__PURE__ */ new Map();
|
|
1080
|
+
knownKeys = /* @__PURE__ */ new Set();
|
|
1081
|
+
maxKnownKeys;
|
|
1082
|
+
constructor(options = {}) {
|
|
1083
|
+
this.maxKnownKeys = options.maxKnownKeys;
|
|
780
1084
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1085
|
+
async touch(key) {
|
|
1086
|
+
this.knownKeys.add(key);
|
|
1087
|
+
this.pruneKnownKeysIfNeeded();
|
|
1088
|
+
}
|
|
1089
|
+
async track(key, tags) {
|
|
1090
|
+
this.knownKeys.add(key);
|
|
1091
|
+
this.pruneKnownKeysIfNeeded();
|
|
1092
|
+
if (tags.length === 0) {
|
|
1093
|
+
return;
|
|
788
1094
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
reject(e);
|
|
1095
|
+
const existingTags = this.keyToTags.get(key);
|
|
1096
|
+
if (existingTags) {
|
|
1097
|
+
for (const tag of existingTags) {
|
|
1098
|
+
this.tagToKeys.get(tag)?.delete(key);
|
|
794
1099
|
}
|
|
795
1100
|
}
|
|
796
|
-
|
|
797
|
-
|
|
1101
|
+
const tagSet = new Set(tags);
|
|
1102
|
+
this.keyToTags.set(key, tagSet);
|
|
1103
|
+
for (const tag of tagSet) {
|
|
1104
|
+
const keys = this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set();
|
|
1105
|
+
keys.add(key);
|
|
1106
|
+
this.tagToKeys.set(tag, keys);
|
|
798
1107
|
}
|
|
799
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
800
|
-
});
|
|
801
|
-
};
|
|
802
|
-
var Mutex = class {
|
|
803
|
-
constructor(cancelError) {
|
|
804
|
-
this._semaphore = new Semaphore(1, cancelError);
|
|
805
1108
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
const [, releaser] = yield this._semaphore.acquire();
|
|
809
|
-
return releaser;
|
|
810
|
-
});
|
|
1109
|
+
async remove(key) {
|
|
1110
|
+
this.removeKey(key);
|
|
811
1111
|
}
|
|
812
|
-
|
|
813
|
-
return this.
|
|
1112
|
+
async keysForTag(tag) {
|
|
1113
|
+
return [...this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()];
|
|
814
1114
|
}
|
|
815
|
-
|
|
816
|
-
return this.
|
|
1115
|
+
async keysForPrefix(prefix) {
|
|
1116
|
+
return [...this.knownKeys].filter((key) => key.startsWith(prefix));
|
|
817
1117
|
}
|
|
818
|
-
|
|
819
|
-
return this.
|
|
1118
|
+
async tagsForKey(key) {
|
|
1119
|
+
return [...this.keyToTags.get(key) ?? /* @__PURE__ */ new Set()];
|
|
820
1120
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
this._semaphore.release();
|
|
1121
|
+
async matchPattern(pattern) {
|
|
1122
|
+
return [...this.knownKeys].filter((key) => PatternMatcher.matches(pattern, key));
|
|
824
1123
|
}
|
|
825
|
-
|
|
826
|
-
|
|
1124
|
+
async clear() {
|
|
1125
|
+
this.tagToKeys.clear();
|
|
1126
|
+
this.keyToTags.clear();
|
|
1127
|
+
this.knownKeys.clear();
|
|
1128
|
+
}
|
|
1129
|
+
pruneKnownKeysIfNeeded() {
|
|
1130
|
+
if (this.maxKnownKeys === void 0 || this.knownKeys.size <= this.maxKnownKeys) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const toRemove = Math.ceil(this.maxKnownKeys * 0.1);
|
|
1134
|
+
let removed = 0;
|
|
1135
|
+
for (const key of this.knownKeys) {
|
|
1136
|
+
if (removed >= toRemove) {
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
this.removeKey(key);
|
|
1140
|
+
removed += 1;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
removeKey(key) {
|
|
1144
|
+
this.knownKeys.delete(key);
|
|
1145
|
+
const tags = this.keyToTags.get(key);
|
|
1146
|
+
if (!tags) {
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
for (const tag of tags) {
|
|
1150
|
+
const keys = this.tagToKeys.get(tag);
|
|
1151
|
+
if (!keys) {
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
keys.delete(key);
|
|
1155
|
+
if (keys.size === 0) {
|
|
1156
|
+
this.tagToKeys.delete(tag);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
this.keyToTags.delete(key);
|
|
827
1160
|
}
|
|
828
1161
|
};
|
|
829
1162
|
|
|
@@ -905,6 +1238,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
905
1238
|
const maxProfileEntries = options.maxProfileEntries ?? DEFAULT_MAX_PROFILE_ENTRIES;
|
|
906
1239
|
this.ttlResolver = new TtlResolver({ maxProfileEntries });
|
|
907
1240
|
this.circuitBreakerManager = new CircuitBreakerManager({ maxEntries: maxProfileEntries });
|
|
1241
|
+
this.currentGeneration = options.generation;
|
|
908
1242
|
if (options.publishSetInvalidation !== void 0) {
|
|
909
1243
|
console.warn(
|
|
910
1244
|
"[layercache] CacheStackOptions.publishSetInvalidation is deprecated. Use broadcastL1Invalidation instead."
|
|
@@ -913,21 +1247,27 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
913
1247
|
const debugEnv = process.env.DEBUG?.split(",").includes("layercache:debug") ?? false;
|
|
914
1248
|
this.logger = typeof options.logger === "object" ? options.logger : new DebugLogger(Boolean(options.logger) || debugEnv);
|
|
915
1249
|
this.tagIndex = options.tagIndex ?? new TagIndex();
|
|
1250
|
+
this.initializeWriteBehind(options.writeBehind);
|
|
916
1251
|
this.startup = this.initialize();
|
|
917
1252
|
}
|
|
918
1253
|
layers;
|
|
919
1254
|
options;
|
|
920
1255
|
stampedeGuard = new StampedeGuard();
|
|
921
1256
|
metricsCollector = new MetricsCollector();
|
|
922
|
-
instanceId = (
|
|
1257
|
+
instanceId = createInstanceId();
|
|
923
1258
|
startup;
|
|
924
1259
|
unsubscribeInvalidation;
|
|
925
1260
|
logger;
|
|
926
1261
|
tagIndex;
|
|
1262
|
+
fetchRateLimiter = new FetchRateLimiter();
|
|
927
1263
|
backgroundRefreshes = /* @__PURE__ */ new Map();
|
|
928
1264
|
layerDegradedUntil = /* @__PURE__ */ new Map();
|
|
929
1265
|
ttlResolver;
|
|
930
1266
|
circuitBreakerManager;
|
|
1267
|
+
currentGeneration;
|
|
1268
|
+
writeBehindQueue = [];
|
|
1269
|
+
writeBehindTimer;
|
|
1270
|
+
writeBehindFlushPromise;
|
|
931
1271
|
isDisconnecting = false;
|
|
932
1272
|
disconnectPromise;
|
|
933
1273
|
/**
|
|
@@ -937,9 +1277,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
937
1277
|
* and no `fetcher` is provided.
|
|
938
1278
|
*/
|
|
939
1279
|
async get(key, fetcher, options) {
|
|
940
|
-
const normalizedKey = this.validateCacheKey(key);
|
|
1280
|
+
const normalizedKey = this.qualifyKey(this.validateCacheKey(key));
|
|
941
1281
|
this.validateWriteOptions(options);
|
|
942
|
-
await this.
|
|
1282
|
+
await this.awaitStartup("get");
|
|
943
1283
|
const hit = await this.readFromLayers(normalizedKey, options, "allow-stale");
|
|
944
1284
|
if (hit.found) {
|
|
945
1285
|
this.ttlResolver.recordAccess(normalizedKey);
|
|
@@ -1004,8 +1344,8 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1004
1344
|
* Returns true if the given key exists and is not expired in any layer.
|
|
1005
1345
|
*/
|
|
1006
1346
|
async has(key) {
|
|
1007
|
-
const normalizedKey = this.validateCacheKey(key);
|
|
1008
|
-
await this.
|
|
1347
|
+
const normalizedKey = this.qualifyKey(this.validateCacheKey(key));
|
|
1348
|
+
await this.awaitStartup("has");
|
|
1009
1349
|
for (const layer of this.layers) {
|
|
1010
1350
|
if (this.shouldSkipLayer(layer)) {
|
|
1011
1351
|
continue;
|
|
@@ -1035,8 +1375,8 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1035
1375
|
* that has it, or null if the key is not found / has no TTL.
|
|
1036
1376
|
*/
|
|
1037
1377
|
async ttl(key) {
|
|
1038
|
-
const normalizedKey = this.validateCacheKey(key);
|
|
1039
|
-
await this.
|
|
1378
|
+
const normalizedKey = this.qualifyKey(this.validateCacheKey(key));
|
|
1379
|
+
await this.awaitStartup("ttl");
|
|
1040
1380
|
for (const layer of this.layers) {
|
|
1041
1381
|
if (this.shouldSkipLayer(layer)) {
|
|
1042
1382
|
continue;
|
|
@@ -1057,17 +1397,17 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1057
1397
|
* Stores a value in all cache layers. Overwrites any existing value.
|
|
1058
1398
|
*/
|
|
1059
1399
|
async set(key, value, options) {
|
|
1060
|
-
const normalizedKey = this.validateCacheKey(key);
|
|
1400
|
+
const normalizedKey = this.qualifyKey(this.validateCacheKey(key));
|
|
1061
1401
|
this.validateWriteOptions(options);
|
|
1062
|
-
await this.
|
|
1402
|
+
await this.awaitStartup("set");
|
|
1063
1403
|
await this.storeEntry(normalizedKey, "value", value, options);
|
|
1064
1404
|
}
|
|
1065
1405
|
/**
|
|
1066
1406
|
* Deletes the key from all layers and publishes an invalidation message.
|
|
1067
1407
|
*/
|
|
1068
1408
|
async delete(key) {
|
|
1069
|
-
const normalizedKey = this.validateCacheKey(key);
|
|
1070
|
-
await this.
|
|
1409
|
+
const normalizedKey = this.qualifyKey(this.validateCacheKey(key));
|
|
1410
|
+
await this.awaitStartup("delete");
|
|
1071
1411
|
await this.deleteKeys([normalizedKey]);
|
|
1072
1412
|
await this.publishInvalidation({
|
|
1073
1413
|
scope: "key",
|
|
@@ -1077,7 +1417,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1077
1417
|
});
|
|
1078
1418
|
}
|
|
1079
1419
|
async clear() {
|
|
1080
|
-
await this.
|
|
1420
|
+
await this.awaitStartup("clear");
|
|
1081
1421
|
await Promise.all(this.layers.map((layer) => layer.clear()));
|
|
1082
1422
|
await this.tagIndex.clear();
|
|
1083
1423
|
this.ttlResolver.clearProfiles();
|
|
@@ -1093,23 +1433,25 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1093
1433
|
if (keys.length === 0) {
|
|
1094
1434
|
return;
|
|
1095
1435
|
}
|
|
1096
|
-
await this.
|
|
1436
|
+
await this.awaitStartup("mdelete");
|
|
1097
1437
|
const normalizedKeys = keys.map((k) => this.validateCacheKey(k));
|
|
1098
|
-
|
|
1438
|
+
const cacheKeys = normalizedKeys.map((key) => this.qualifyKey(key));
|
|
1439
|
+
await this.deleteKeys(cacheKeys);
|
|
1099
1440
|
await this.publishInvalidation({
|
|
1100
1441
|
scope: "keys",
|
|
1101
|
-
keys:
|
|
1442
|
+
keys: cacheKeys,
|
|
1102
1443
|
sourceId: this.instanceId,
|
|
1103
1444
|
operation: "delete"
|
|
1104
1445
|
});
|
|
1105
1446
|
}
|
|
1106
1447
|
async mget(entries) {
|
|
1448
|
+
this.assertActive("mget");
|
|
1107
1449
|
if (entries.length === 0) {
|
|
1108
1450
|
return [];
|
|
1109
1451
|
}
|
|
1110
1452
|
const normalizedEntries = entries.map((entry) => ({
|
|
1111
1453
|
...entry,
|
|
1112
|
-
key: this.validateCacheKey(entry.key)
|
|
1454
|
+
key: this.qualifyKey(this.validateCacheKey(entry.key))
|
|
1113
1455
|
}));
|
|
1114
1456
|
normalizedEntries.forEach((entry) => this.validateWriteOptions(entry.options));
|
|
1115
1457
|
const canFastPath = normalizedEntries.every((entry) => entry.fetch === void 0 && entry.options === void 0);
|
|
@@ -1135,7 +1477,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1135
1477
|
})
|
|
1136
1478
|
);
|
|
1137
1479
|
}
|
|
1138
|
-
await this.
|
|
1480
|
+
await this.awaitStartup("mget");
|
|
1139
1481
|
const pending = /* @__PURE__ */ new Set();
|
|
1140
1482
|
const indexesByKey = /* @__PURE__ */ new Map();
|
|
1141
1483
|
const resultsByKey = /* @__PURE__ */ new Map();
|
|
@@ -1183,14 +1525,17 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1183
1525
|
return normalizedEntries.map((entry) => resultsByKey.get(entry.key) ?? null);
|
|
1184
1526
|
}
|
|
1185
1527
|
async mset(entries) {
|
|
1528
|
+
this.assertActive("mset");
|
|
1186
1529
|
const normalizedEntries = entries.map((entry) => ({
|
|
1187
1530
|
...entry,
|
|
1188
|
-
key: this.validateCacheKey(entry.key)
|
|
1531
|
+
key: this.qualifyKey(this.validateCacheKey(entry.key))
|
|
1189
1532
|
}));
|
|
1190
1533
|
normalizedEntries.forEach((entry) => this.validateWriteOptions(entry.options));
|
|
1191
|
-
await
|
|
1534
|
+
await this.awaitStartup("mset");
|
|
1535
|
+
await this.writeBatch(normalizedEntries);
|
|
1192
1536
|
}
|
|
1193
1537
|
async warm(entries, options = {}) {
|
|
1538
|
+
this.assertActive("warm");
|
|
1194
1539
|
const concurrency = Math.max(1, options.concurrency ?? 4);
|
|
1195
1540
|
const total = entries.length;
|
|
1196
1541
|
let completed = 0;
|
|
@@ -1239,14 +1584,31 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1239
1584
|
return new CacheNamespace(this, prefix);
|
|
1240
1585
|
}
|
|
1241
1586
|
async invalidateByTag(tag) {
|
|
1242
|
-
await this.
|
|
1587
|
+
await this.awaitStartup("invalidateByTag");
|
|
1243
1588
|
const keys = await this.tagIndex.keysForTag(tag);
|
|
1244
1589
|
await this.deleteKeys(keys);
|
|
1245
1590
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1246
1591
|
}
|
|
1592
|
+
async invalidateByTags(tags, mode = "any") {
|
|
1593
|
+
if (tags.length === 0) {
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
await this.awaitStartup("invalidateByTags");
|
|
1597
|
+
const keysByTag = await Promise.all(tags.map((tag) => this.tagIndex.keysForTag(tag)));
|
|
1598
|
+
const keys = mode === "all" ? this.intersectKeys(keysByTag) : [...new Set(keysByTag.flat())];
|
|
1599
|
+
await this.deleteKeys(keys);
|
|
1600
|
+
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1601
|
+
}
|
|
1247
1602
|
async invalidateByPattern(pattern) {
|
|
1248
|
-
await this.
|
|
1249
|
-
const keys = await this.tagIndex.matchPattern(pattern);
|
|
1603
|
+
await this.awaitStartup("invalidateByPattern");
|
|
1604
|
+
const keys = await this.tagIndex.matchPattern(this.qualifyPattern(pattern));
|
|
1605
|
+
await this.deleteKeys(keys);
|
|
1606
|
+
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1607
|
+
}
|
|
1608
|
+
async invalidateByPrefix(prefix) {
|
|
1609
|
+
await this.awaitStartup("invalidateByPrefix");
|
|
1610
|
+
const qualifiedPrefix = this.qualifyKey(this.validateCacheKey(prefix));
|
|
1611
|
+
const keys = this.tagIndex.keysForPrefix ? await this.tagIndex.keysForPrefix(qualifiedPrefix) : await this.tagIndex.matchPattern(`${qualifiedPrefix}*`);
|
|
1250
1612
|
await this.deleteKeys(keys);
|
|
1251
1613
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1252
1614
|
}
|
|
@@ -1273,14 +1635,43 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1273
1635
|
getHitRate() {
|
|
1274
1636
|
return this.metricsCollector.hitRate();
|
|
1275
1637
|
}
|
|
1638
|
+
async healthCheck() {
|
|
1639
|
+
await this.startup;
|
|
1640
|
+
return Promise.all(
|
|
1641
|
+
this.layers.map(async (layer) => {
|
|
1642
|
+
const startedAt = performance.now();
|
|
1643
|
+
try {
|
|
1644
|
+
const healthy = layer.ping ? await layer.ping() : true;
|
|
1645
|
+
return {
|
|
1646
|
+
layer: layer.name,
|
|
1647
|
+
healthy,
|
|
1648
|
+
latencyMs: performance.now() - startedAt
|
|
1649
|
+
};
|
|
1650
|
+
} catch (error) {
|
|
1651
|
+
return {
|
|
1652
|
+
layer: layer.name,
|
|
1653
|
+
healthy: false,
|
|
1654
|
+
latencyMs: performance.now() - startedAt,
|
|
1655
|
+
error: this.formatError(error)
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
})
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
bumpGeneration(nextGeneration) {
|
|
1662
|
+
const current = this.currentGeneration ?? 0;
|
|
1663
|
+
this.currentGeneration = nextGeneration ?? current + 1;
|
|
1664
|
+
return this.currentGeneration;
|
|
1665
|
+
}
|
|
1276
1666
|
/**
|
|
1277
1667
|
* Returns detailed metadata about a single cache key: which layers contain it,
|
|
1278
1668
|
* remaining fresh/stale/error TTLs, and associated tags.
|
|
1279
1669
|
* Returns `null` if the key does not exist in any layer.
|
|
1280
1670
|
*/
|
|
1281
1671
|
async inspect(key) {
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1672
|
+
const userKey = this.validateCacheKey(key);
|
|
1673
|
+
const normalizedKey = this.qualifyKey(userKey);
|
|
1674
|
+
await this.awaitStartup("inspect");
|
|
1284
1675
|
const foundInLayers = [];
|
|
1285
1676
|
let freshTtlSeconds = null;
|
|
1286
1677
|
let staleTtlSeconds = null;
|
|
@@ -1311,10 +1702,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1311
1702
|
return null;
|
|
1312
1703
|
}
|
|
1313
1704
|
const tags = await this.getTagsForKey(normalizedKey);
|
|
1314
|
-
return { key:
|
|
1705
|
+
return { key: userKey, foundInLayers, freshTtlSeconds, staleTtlSeconds, errorTtlSeconds, isStale, tags };
|
|
1315
1706
|
}
|
|
1316
1707
|
async exportState() {
|
|
1317
|
-
await this.
|
|
1708
|
+
await this.awaitStartup("exportState");
|
|
1318
1709
|
const exported = /* @__PURE__ */ new Map();
|
|
1319
1710
|
for (const layer of this.layers) {
|
|
1320
1711
|
if (!layer.keys) {
|
|
@@ -1322,15 +1713,16 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1322
1713
|
}
|
|
1323
1714
|
const keys = await layer.keys();
|
|
1324
1715
|
for (const key of keys) {
|
|
1325
|
-
|
|
1716
|
+
const exportedKey = this.stripQualifiedKey(key);
|
|
1717
|
+
if (exported.has(exportedKey)) {
|
|
1326
1718
|
continue;
|
|
1327
1719
|
}
|
|
1328
1720
|
const stored = await this.readLayerEntry(layer, key);
|
|
1329
1721
|
if (stored === null) {
|
|
1330
1722
|
continue;
|
|
1331
1723
|
}
|
|
1332
|
-
exported.set(
|
|
1333
|
-
key,
|
|
1724
|
+
exported.set(exportedKey, {
|
|
1725
|
+
key: exportedKey,
|
|
1334
1726
|
value: stored,
|
|
1335
1727
|
ttl: remainingStoredTtlSeconds(stored)
|
|
1336
1728
|
});
|
|
@@ -1339,20 +1731,25 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1339
1731
|
return [...exported.values()];
|
|
1340
1732
|
}
|
|
1341
1733
|
async importState(entries) {
|
|
1342
|
-
await this.
|
|
1734
|
+
await this.awaitStartup("importState");
|
|
1343
1735
|
await Promise.all(
|
|
1344
1736
|
entries.map(async (entry) => {
|
|
1345
|
-
|
|
1346
|
-
await this.
|
|
1737
|
+
const qualifiedKey = this.qualifyKey(entry.key);
|
|
1738
|
+
await Promise.all(this.layers.map((layer) => layer.set(qualifiedKey, entry.value, entry.ttl)));
|
|
1739
|
+
await this.tagIndex.touch(qualifiedKey);
|
|
1347
1740
|
})
|
|
1348
1741
|
);
|
|
1349
1742
|
}
|
|
1350
1743
|
async persistToFile(filePath) {
|
|
1744
|
+
this.assertActive("persistToFile");
|
|
1351
1745
|
const snapshot = await this.exportState();
|
|
1352
|
-
|
|
1746
|
+
const { promises: fs } = await import("fs");
|
|
1747
|
+
await fs.writeFile(filePath, JSON.stringify(snapshot, null, 2), "utf8");
|
|
1353
1748
|
}
|
|
1354
1749
|
async restoreFromFile(filePath) {
|
|
1355
|
-
|
|
1750
|
+
this.assertActive("restoreFromFile");
|
|
1751
|
+
const { promises: fs } = await import("fs");
|
|
1752
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
1356
1753
|
let parsed;
|
|
1357
1754
|
try {
|
|
1358
1755
|
parsed = JSON.parse(raw, (_key, value) => {
|
|
@@ -1375,7 +1772,13 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1375
1772
|
this.disconnectPromise = (async () => {
|
|
1376
1773
|
await this.startup;
|
|
1377
1774
|
await this.unsubscribeInvalidation?.();
|
|
1775
|
+
await this.flushWriteBehindQueue();
|
|
1378
1776
|
await Promise.allSettled([...this.backgroundRefreshes.values()]);
|
|
1777
|
+
if (this.writeBehindTimer) {
|
|
1778
|
+
clearInterval(this.writeBehindTimer);
|
|
1779
|
+
this.writeBehindTimer = void 0;
|
|
1780
|
+
}
|
|
1781
|
+
await Promise.allSettled(this.layers.map((layer) => layer.dispose?.() ?? Promise.resolve()));
|
|
1379
1782
|
})();
|
|
1380
1783
|
}
|
|
1381
1784
|
await this.disconnectPromise;
|
|
@@ -1435,7 +1838,11 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1435
1838
|
const fetchStart = Date.now();
|
|
1436
1839
|
let fetched;
|
|
1437
1840
|
try {
|
|
1438
|
-
fetched = await
|
|
1841
|
+
fetched = await this.fetchRateLimiter.schedule(
|
|
1842
|
+
options?.fetcherRateLimit ?? this.options.fetcherRateLimit,
|
|
1843
|
+
{ key, fetcher },
|
|
1844
|
+
fetcher
|
|
1845
|
+
);
|
|
1439
1846
|
this.circuitBreakerManager.recordSuccess(key);
|
|
1440
1847
|
this.logger.debug?.("fetch", { key, durationMs: Date.now() - fetchStart });
|
|
1441
1848
|
} catch (error) {
|
|
@@ -1469,6 +1876,61 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1469
1876
|
await this.publishInvalidation({ scope: "key", keys: [key], sourceId: this.instanceId, operation: "write" });
|
|
1470
1877
|
}
|
|
1471
1878
|
}
|
|
1879
|
+
async writeBatch(entries) {
|
|
1880
|
+
const now = Date.now();
|
|
1881
|
+
const entriesByLayer = /* @__PURE__ */ new Map();
|
|
1882
|
+
const immediateOperations = [];
|
|
1883
|
+
const deferredOperations = [];
|
|
1884
|
+
for (const entry of entries) {
|
|
1885
|
+
for (const layer of this.layers) {
|
|
1886
|
+
if (this.shouldSkipLayer(layer)) {
|
|
1887
|
+
continue;
|
|
1888
|
+
}
|
|
1889
|
+
const layerEntry = this.buildLayerSetEntry(layer, entry.key, "value", entry.value, entry.options, now);
|
|
1890
|
+
const bucket = entriesByLayer.get(layer) ?? [];
|
|
1891
|
+
bucket.push(layerEntry);
|
|
1892
|
+
entriesByLayer.set(layer, bucket);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
for (const [layer, layerEntries] of entriesByLayer.entries()) {
|
|
1896
|
+
const operation = async () => {
|
|
1897
|
+
try {
|
|
1898
|
+
if (layer.setMany) {
|
|
1899
|
+
await layer.setMany(layerEntries);
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
await Promise.all(layerEntries.map((entry) => layer.set(entry.key, entry.value, entry.ttl)));
|
|
1903
|
+
} catch (error) {
|
|
1904
|
+
await this.handleLayerFailure(layer, "write", error);
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
1907
|
+
if (this.shouldWriteBehind(layer)) {
|
|
1908
|
+
deferredOperations.push(operation);
|
|
1909
|
+
} else {
|
|
1910
|
+
immediateOperations.push(operation);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
await this.executeLayerOperations(immediateOperations, { key: "batch", action: "mset" });
|
|
1914
|
+
await Promise.all(deferredOperations.map((operation) => this.enqueueWriteBehind(operation)));
|
|
1915
|
+
for (const entry of entries) {
|
|
1916
|
+
if (entry.options?.tags) {
|
|
1917
|
+
await this.tagIndex.track(entry.key, entry.options.tags);
|
|
1918
|
+
} else {
|
|
1919
|
+
await this.tagIndex.touch(entry.key);
|
|
1920
|
+
}
|
|
1921
|
+
this.metricsCollector.increment("sets");
|
|
1922
|
+
this.logger.debug?.("set", { key: entry.key, kind: "value", tags: entry.options?.tags });
|
|
1923
|
+
this.emit("set", { key: entry.key, kind: "value", tags: entry.options?.tags });
|
|
1924
|
+
}
|
|
1925
|
+
if (this.shouldBroadcastL1Invalidation()) {
|
|
1926
|
+
await this.publishInvalidation({
|
|
1927
|
+
scope: "keys",
|
|
1928
|
+
keys: entries.map((entry) => entry.key),
|
|
1929
|
+
sourceId: this.instanceId,
|
|
1930
|
+
operation: "write"
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1472
1934
|
async readFromLayers(key, options, mode) {
|
|
1473
1935
|
let sawRetainableValue = false;
|
|
1474
1936
|
for (let index = 0; index < this.layers.length; index += 1) {
|
|
@@ -1552,33 +2014,28 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1552
2014
|
}
|
|
1553
2015
|
async writeAcrossLayers(key, kind, value, options) {
|
|
1554
2016
|
const now = Date.now();
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
options
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
});
|
|
1574
|
-
const ttl = remainingStoredTtlSeconds(payload, now) ?? freshTtl;
|
|
1575
|
-
try {
|
|
1576
|
-
await layer.set(key, payload, ttl);
|
|
1577
|
-
} catch (error) {
|
|
1578
|
-
await this.handleLayerFailure(layer, "write", error);
|
|
2017
|
+
const immediateOperations = [];
|
|
2018
|
+
const deferredOperations = [];
|
|
2019
|
+
for (const layer of this.layers) {
|
|
2020
|
+
const operation = async () => {
|
|
2021
|
+
if (this.shouldSkipLayer(layer)) {
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
const entry = this.buildLayerSetEntry(layer, key, kind, value, options, now);
|
|
2025
|
+
try {
|
|
2026
|
+
await layer.set(entry.key, entry.value, entry.ttl);
|
|
2027
|
+
} catch (error) {
|
|
2028
|
+
await this.handleLayerFailure(layer, "write", error);
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
if (this.shouldWriteBehind(layer)) {
|
|
2032
|
+
deferredOperations.push(operation);
|
|
2033
|
+
} else {
|
|
2034
|
+
immediateOperations.push(operation);
|
|
1579
2035
|
}
|
|
1580
|
-
}
|
|
1581
|
-
await this.executeLayerOperations(
|
|
2036
|
+
}
|
|
2037
|
+
await this.executeLayerOperations(immediateOperations, { key, action: kind === "empty" ? "negative-set" : "set" });
|
|
2038
|
+
await Promise.all(deferredOperations.map((operation) => this.enqueueWriteBehind(operation)));
|
|
1582
2039
|
}
|
|
1583
2040
|
async executeLayerOperations(operations, context) {
|
|
1584
2041
|
if (this.options.writePolicy !== "best-effort") {
|
|
@@ -1602,8 +2059,17 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1602
2059
|
);
|
|
1603
2060
|
}
|
|
1604
2061
|
}
|
|
1605
|
-
resolveFreshTtl(key, layerName, kind, options, fallbackTtl) {
|
|
1606
|
-
return this.ttlResolver.resolveFreshTtl(
|
|
2062
|
+
resolveFreshTtl(key, layerName, kind, options, fallbackTtl, value) {
|
|
2063
|
+
return this.ttlResolver.resolveFreshTtl(
|
|
2064
|
+
key,
|
|
2065
|
+
layerName,
|
|
2066
|
+
kind,
|
|
2067
|
+
options,
|
|
2068
|
+
fallbackTtl,
|
|
2069
|
+
this.options.negativeTtl,
|
|
2070
|
+
void 0,
|
|
2071
|
+
value
|
|
2072
|
+
);
|
|
1607
2073
|
}
|
|
1608
2074
|
resolveLayerSeconds(layerName, override, globalDefault, fallback) {
|
|
1609
2075
|
return this.ttlResolver.resolveLayerSeconds(layerName, override, globalDefault, fallback);
|
|
@@ -1632,7 +2098,8 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1632
2098
|
return {
|
|
1633
2099
|
leaseMs: this.options.singleFlightLeaseMs ?? DEFAULT_SINGLE_FLIGHT_LEASE_MS,
|
|
1634
2100
|
waitTimeoutMs: this.options.singleFlightTimeoutMs ?? DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS,
|
|
1635
|
-
pollIntervalMs: this.options.singleFlightPollMs ?? DEFAULT_SINGLE_FLIGHT_POLL_MS
|
|
2101
|
+
pollIntervalMs: this.options.singleFlightPollMs ?? DEFAULT_SINGLE_FLIGHT_POLL_MS,
|
|
2102
|
+
renewIntervalMs: this.options.singleFlightRenewIntervalMs
|
|
1636
2103
|
};
|
|
1637
2104
|
}
|
|
1638
2105
|
async deleteKeys(keys) {
|
|
@@ -1697,6 +2164,105 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1697
2164
|
shouldBroadcastL1Invalidation() {
|
|
1698
2165
|
return this.options.broadcastL1Invalidation ?? this.options.publishSetInvalidation ?? true;
|
|
1699
2166
|
}
|
|
2167
|
+
initializeWriteBehind(options) {
|
|
2168
|
+
if (this.options.writeStrategy !== "write-behind") {
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
const flushIntervalMs = options?.flushIntervalMs;
|
|
2172
|
+
if (!flushIntervalMs || flushIntervalMs <= 0) {
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
this.writeBehindTimer = setInterval(() => {
|
|
2176
|
+
void this.flushWriteBehindQueue();
|
|
2177
|
+
}, flushIntervalMs);
|
|
2178
|
+
this.writeBehindTimer.unref?.();
|
|
2179
|
+
}
|
|
2180
|
+
shouldWriteBehind(layer) {
|
|
2181
|
+
return this.options.writeStrategy === "write-behind" && !layer.isLocal;
|
|
2182
|
+
}
|
|
2183
|
+
async enqueueWriteBehind(operation) {
|
|
2184
|
+
this.writeBehindQueue.push(operation);
|
|
2185
|
+
const batchSize = this.options.writeBehind?.batchSize ?? 100;
|
|
2186
|
+
const maxQueueSize = this.options.writeBehind?.maxQueueSize ?? batchSize * 10;
|
|
2187
|
+
if (this.writeBehindQueue.length >= batchSize) {
|
|
2188
|
+
await this.flushWriteBehindQueue();
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
if (this.writeBehindQueue.length >= maxQueueSize) {
|
|
2192
|
+
await this.flushWriteBehindQueue();
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
async flushWriteBehindQueue() {
|
|
2196
|
+
if (this.writeBehindFlushPromise || this.writeBehindQueue.length === 0) {
|
|
2197
|
+
await this.writeBehindFlushPromise;
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
const batchSize = this.options.writeBehind?.batchSize ?? 100;
|
|
2201
|
+
const batch = this.writeBehindQueue.splice(0, batchSize);
|
|
2202
|
+
this.writeBehindFlushPromise = (async () => {
|
|
2203
|
+
await Promise.allSettled(batch.map((operation) => operation()));
|
|
2204
|
+
})();
|
|
2205
|
+
await this.writeBehindFlushPromise;
|
|
2206
|
+
this.writeBehindFlushPromise = void 0;
|
|
2207
|
+
if (this.writeBehindQueue.length > 0) {
|
|
2208
|
+
await this.flushWriteBehindQueue();
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
buildLayerSetEntry(layer, key, kind, value, options, now) {
|
|
2212
|
+
const freshTtl = this.resolveFreshTtl(key, layer.name, kind, options, layer.defaultTtl, value);
|
|
2213
|
+
const staleWhileRevalidate = this.resolveLayerSeconds(
|
|
2214
|
+
layer.name,
|
|
2215
|
+
options?.staleWhileRevalidate,
|
|
2216
|
+
this.options.staleWhileRevalidate
|
|
2217
|
+
);
|
|
2218
|
+
const staleIfError = this.resolveLayerSeconds(layer.name, options?.staleIfError, this.options.staleIfError);
|
|
2219
|
+
const payload = createStoredValueEnvelope({
|
|
2220
|
+
kind,
|
|
2221
|
+
value,
|
|
2222
|
+
freshTtlSeconds: freshTtl,
|
|
2223
|
+
staleWhileRevalidateSeconds: staleWhileRevalidate,
|
|
2224
|
+
staleIfErrorSeconds: staleIfError,
|
|
2225
|
+
now
|
|
2226
|
+
});
|
|
2227
|
+
const ttl = remainingStoredTtlSeconds(payload, now) ?? freshTtl;
|
|
2228
|
+
return {
|
|
2229
|
+
key,
|
|
2230
|
+
value: payload,
|
|
2231
|
+
ttl
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
intersectKeys(groups) {
|
|
2235
|
+
if (groups.length === 0) {
|
|
2236
|
+
return [];
|
|
2237
|
+
}
|
|
2238
|
+
const [firstGroup, ...rest] = groups;
|
|
2239
|
+
if (!firstGroup) {
|
|
2240
|
+
return [];
|
|
2241
|
+
}
|
|
2242
|
+
const restSets = rest.map((group) => new Set(group));
|
|
2243
|
+
return [...new Set(firstGroup)].filter((key) => restSets.every((group) => group.has(key)));
|
|
2244
|
+
}
|
|
2245
|
+
qualifyKey(key) {
|
|
2246
|
+
const prefix = this.generationPrefix();
|
|
2247
|
+
return prefix ? `${prefix}${key}` : key;
|
|
2248
|
+
}
|
|
2249
|
+
qualifyPattern(pattern) {
|
|
2250
|
+
const prefix = this.generationPrefix();
|
|
2251
|
+
return prefix ? `${prefix}${pattern}` : pattern;
|
|
2252
|
+
}
|
|
2253
|
+
stripQualifiedKey(key) {
|
|
2254
|
+
const prefix = this.generationPrefix();
|
|
2255
|
+
if (!prefix || !key.startsWith(prefix)) {
|
|
2256
|
+
return key;
|
|
2257
|
+
}
|
|
2258
|
+
return key.slice(prefix.length);
|
|
2259
|
+
}
|
|
2260
|
+
generationPrefix() {
|
|
2261
|
+
if (this.currentGeneration === void 0) {
|
|
2262
|
+
return "";
|
|
2263
|
+
}
|
|
2264
|
+
return `v${this.currentGeneration}:`;
|
|
2265
|
+
}
|
|
1700
2266
|
async deleteKeysFromLayers(layers, keys) {
|
|
1701
2267
|
await Promise.all(
|
|
1702
2268
|
layers.map(async (layer) => {
|
|
@@ -1738,8 +2304,13 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1738
2304
|
this.validatePositiveNumber("singleFlightLeaseMs", this.options.singleFlightLeaseMs);
|
|
1739
2305
|
this.validatePositiveNumber("singleFlightTimeoutMs", this.options.singleFlightTimeoutMs);
|
|
1740
2306
|
this.validatePositiveNumber("singleFlightPollMs", this.options.singleFlightPollMs);
|
|
2307
|
+
this.validatePositiveNumber("singleFlightRenewIntervalMs", this.options.singleFlightRenewIntervalMs);
|
|
2308
|
+
this.validateRateLimitOptions("fetcherRateLimit", this.options.fetcherRateLimit);
|
|
1741
2309
|
this.validateAdaptiveTtlOptions(this.options.adaptiveTtl);
|
|
1742
2310
|
this.validateCircuitBreakerOptions(this.options.circuitBreaker);
|
|
2311
|
+
if (this.options.generation !== void 0) {
|
|
2312
|
+
this.validateNonNegativeNumber("generation", this.options.generation);
|
|
2313
|
+
}
|
|
1743
2314
|
}
|
|
1744
2315
|
validateWriteOptions(options) {
|
|
1745
2316
|
if (!options) {
|
|
@@ -1751,8 +2322,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1751
2322
|
this.validateLayerNumberOption("options.staleIfError", options.staleIfError);
|
|
1752
2323
|
this.validateLayerNumberOption("options.ttlJitter", options.ttlJitter);
|
|
1753
2324
|
this.validateLayerNumberOption("options.refreshAhead", options.refreshAhead);
|
|
2325
|
+
this.validateTtlPolicy("options.ttlPolicy", options.ttlPolicy);
|
|
1754
2326
|
this.validateAdaptiveTtlOptions(options.adaptiveTtl);
|
|
1755
2327
|
this.validateCircuitBreakerOptions(options.circuitBreaker);
|
|
2328
|
+
this.validateRateLimitOptions("options.fetcherRateLimit", options.fetcherRateLimit);
|
|
1756
2329
|
}
|
|
1757
2330
|
validateLayerNumberOption(name, value) {
|
|
1758
2331
|
if (value === void 0) {
|
|
@@ -1777,6 +2350,20 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1777
2350
|
throw new Error(`${name} must be a positive finite number.`);
|
|
1778
2351
|
}
|
|
1779
2352
|
}
|
|
2353
|
+
validateRateLimitOptions(name, options) {
|
|
2354
|
+
if (!options) {
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
this.validatePositiveNumber(`${name}.maxConcurrent`, options.maxConcurrent);
|
|
2358
|
+
this.validatePositiveNumber(`${name}.intervalMs`, options.intervalMs);
|
|
2359
|
+
this.validatePositiveNumber(`${name}.maxPerInterval`, options.maxPerInterval);
|
|
2360
|
+
if (options.scope && !["global", "key", "fetcher"].includes(options.scope)) {
|
|
2361
|
+
throw new Error(`${name}.scope must be one of "global", "key", or "fetcher".`);
|
|
2362
|
+
}
|
|
2363
|
+
if (options.bucketKey !== void 0 && options.bucketKey.length === 0) {
|
|
2364
|
+
throw new Error(`${name}.bucketKey must not be empty.`);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
1780
2367
|
validateNonNegativeNumber(name, value) {
|
|
1781
2368
|
if (!Number.isFinite(value) || value < 0) {
|
|
1782
2369
|
throw new Error(`${name} must be a non-negative finite number.`);
|
|
@@ -1794,6 +2381,26 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1794
2381
|
}
|
|
1795
2382
|
return key;
|
|
1796
2383
|
}
|
|
2384
|
+
validateTtlPolicy(name, policy) {
|
|
2385
|
+
if (!policy || typeof policy === "function" || policy === "until-midnight" || policy === "next-hour") {
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
if ("alignTo" in policy) {
|
|
2389
|
+
this.validatePositiveNumber(`${name}.alignTo`, policy.alignTo);
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
throw new Error(`${name} is invalid.`);
|
|
2393
|
+
}
|
|
2394
|
+
assertActive(operation) {
|
|
2395
|
+
if (this.isDisconnecting) {
|
|
2396
|
+
throw new Error(`CacheStack is disconnecting; cannot perform ${operation}.`);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
async awaitStartup(operation) {
|
|
2400
|
+
this.assertActive(operation);
|
|
2401
|
+
await this.startup;
|
|
2402
|
+
this.assertActive(operation);
|
|
2403
|
+
}
|
|
1797
2404
|
serializeOptions(options) {
|
|
1798
2405
|
return JSON.stringify(this.normalizeForSerialization(options) ?? null);
|
|
1799
2406
|
}
|
|
@@ -1899,6 +2506,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1899
2506
|
return value;
|
|
1900
2507
|
}
|
|
1901
2508
|
};
|
|
2509
|
+
function createInstanceId() {
|
|
2510
|
+
return globalThis.crypto?.randomUUID?.() ?? `layercache-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2511
|
+
}
|
|
1902
2512
|
|
|
1903
2513
|
// src/module.ts
|
|
1904
2514
|
var InjectCacheStack = () => (0, import_common.Inject)(CACHE_STACK);
|