@mark1russell7/collections 0.7.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/build/index.d.ts +58 -8
- package/build/index.d.ts.map +1 -1
- package/build/index.js +232 -75
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/behaviors/bounded.ts +52 -7
- package/src/behaviors/evented.ts +75 -25
- package/src/behaviors/lru.ts +22 -24
- package/src/behaviors/ttl.ts +92 -84
- package/src/core/removals.ts +85 -0
- package/src/index.ts +1 -0
package/build/index.js
CHANGED
|
@@ -456,6 +456,38 @@ const onWhen = (emitter, event, predicate, listener) => {
|
|
|
456
456
|
if (predicate(payload)) listener(payload);
|
|
457
457
|
});
|
|
458
458
|
};
|
|
459
|
+
// src/core/removals.ts
|
|
460
|
+
/** The key of the removal channel on a layer. */
|
|
461
|
+
const removals = Symbol.for("@mark1russell7/collections/removals");
|
|
462
|
+
/**
|
|
463
|
+
* This function gives the removal channel of a collection, or `undefined` when no layer of it removes.
|
|
464
|
+
*/
|
|
465
|
+
function removalsOf(collection) {
|
|
466
|
+
const channel = collection[removals];
|
|
467
|
+
if (typeof channel !== "object" || channel === null) return void 0;
|
|
468
|
+
return typeof channel.on === "function" ? channel : void 0;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* This function makes the channel of a removing layer over `next`.
|
|
472
|
+
* The channel forwards each removal below. It calls `below` first, so the layer corrects its own state first.
|
|
473
|
+
*/
|
|
474
|
+
function removalSource(next, below) {
|
|
475
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
476
|
+
const emit = (removal) => {
|
|
477
|
+
for (const listener of [...listeners]) listener(removal);
|
|
478
|
+
};
|
|
479
|
+
removalsOf(next)?.on((removal) => {
|
|
480
|
+
below?.(removal);
|
|
481
|
+
emit(removal);
|
|
482
|
+
});
|
|
483
|
+
return {
|
|
484
|
+
channel: { on(listener) {
|
|
485
|
+
listeners.add(listener);
|
|
486
|
+
return () => void listeners.delete(listener);
|
|
487
|
+
} },
|
|
488
|
+
emit
|
|
489
|
+
};
|
|
490
|
+
}
|
|
459
491
|
// src/storage/listeners.ts
|
|
460
492
|
/**
|
|
461
493
|
* The default handler of the error of a listener. It throws the error again in a microtask, as an
|
|
@@ -4509,6 +4541,7 @@ function boundedList(options) {
|
|
|
4509
4541
|
const { policy = "throw", onOverflow } = options;
|
|
4510
4542
|
const capacity = checkCapacity(options.capacity, "boundedList");
|
|
4511
4543
|
return (next) => {
|
|
4544
|
+
const source = removalSource(next);
|
|
4512
4545
|
const handleOverflow = (element, dropFrom = "first") => {
|
|
4513
4546
|
if (onOverflow) {
|
|
4514
4547
|
const context = {
|
|
@@ -4523,8 +4556,12 @@ function boundedList(options) {
|
|
|
4523
4556
|
case "throw": throw new Error(`List is full (capacity: ${capacity})`);
|
|
4524
4557
|
case "drop-oldest":
|
|
4525
4558
|
if (!next.isEmpty) {
|
|
4526
|
-
|
|
4527
|
-
|
|
4559
|
+
const value = dropFrom === "last" ? next.pop() : next.shift();
|
|
4560
|
+
source.emit({
|
|
4561
|
+
value,
|
|
4562
|
+
end: dropFrom,
|
|
4563
|
+
reason: "drop"
|
|
4564
|
+
});
|
|
4528
4565
|
}
|
|
4529
4566
|
break;
|
|
4530
4567
|
case "drop-newest": return;
|
|
@@ -4536,6 +4573,7 @@ function boundedList(options) {
|
|
|
4536
4573
|
};
|
|
4537
4574
|
const shifted = (index) => policy === "drop-oldest" ? Math.max(0, index - 1) : index;
|
|
4538
4575
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
4576
|
+
if (prop === removals) return source.channel;
|
|
4539
4577
|
if (prop === "capacity") return capacity;
|
|
4540
4578
|
if (prop === "isFull") return target.size >= capacity;
|
|
4541
4579
|
if (prop === "remainingCapacity") return Math.max(0, capacity - target.size);
|
|
@@ -4601,12 +4639,25 @@ function boundedList(options) {
|
|
|
4601
4639
|
};
|
|
4602
4640
|
}
|
|
4603
4641
|
/**
|
|
4642
|
+
* This function adds each element with `add`, one after the other, until `add` refuses one.
|
|
4643
|
+
* It gives true when one element goes in. A throw of `add` stops it, and the earlier elements stay.
|
|
4644
|
+
*/
|
|
4645
|
+
function addEach(elements, add) {
|
|
4646
|
+
let added = false;
|
|
4647
|
+
for (const element of Array.from(elements)) {
|
|
4648
|
+
if (!add(element)) break;
|
|
4649
|
+
added = true;
|
|
4650
|
+
}
|
|
4651
|
+
return added;
|
|
4652
|
+
}
|
|
4653
|
+
/**
|
|
4604
4654
|
* This function makes a bounded Queue middleware.
|
|
4605
4655
|
*/
|
|
4606
4656
|
function boundedQueue(options) {
|
|
4607
4657
|
const { policy = "throw", onOverflow } = options;
|
|
4608
4658
|
const capacity = checkCapacity(options.capacity, "boundedQueue");
|
|
4609
4659
|
return (next) => {
|
|
4660
|
+
const source = removalSource(next);
|
|
4610
4661
|
const handleOverflow = (element) => {
|
|
4611
4662
|
if (onOverflow) {
|
|
4612
4663
|
const context = {
|
|
@@ -4620,7 +4671,11 @@ function boundedQueue(options) {
|
|
|
4620
4671
|
switch (policy) {
|
|
4621
4672
|
case "throw": throw new Error(`Queue is full (capacity: ${capacity})`);
|
|
4622
4673
|
case "drop-oldest":
|
|
4623
|
-
if (!next.isEmpty)
|
|
4674
|
+
if (!next.isEmpty) source.emit({
|
|
4675
|
+
value: next.poll(),
|
|
4676
|
+
end: "first",
|
|
4677
|
+
reason: "drop"
|
|
4678
|
+
});
|
|
4624
4679
|
break;
|
|
4625
4680
|
case "drop-newest":
|
|
4626
4681
|
case "reject": return;
|
|
@@ -4630,6 +4685,7 @@ function boundedQueue(options) {
|
|
|
4630
4685
|
}
|
|
4631
4686
|
};
|
|
4632
4687
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
4688
|
+
if (prop === removals) return source.channel;
|
|
4633
4689
|
if (prop === "capacity") return capacity;
|
|
4634
4690
|
if (prop === "isFull") return target.size >= capacity;
|
|
4635
4691
|
if (prop === "remainingCapacity") return Math.max(0, capacity - target.size);
|
|
@@ -4644,6 +4700,9 @@ function boundedQueue(options) {
|
|
|
4644
4700
|
}
|
|
4645
4701
|
return value.call(target, element);
|
|
4646
4702
|
};
|
|
4703
|
+
case "addAll": return function(elements) {
|
|
4704
|
+
return addEach(elements, (element) => receiver.add(element));
|
|
4705
|
+
};
|
|
4647
4706
|
}
|
|
4648
4707
|
return value;
|
|
4649
4708
|
} });
|
|
@@ -4656,7 +4715,8 @@ function boundedDeque(options) {
|
|
|
4656
4715
|
const { policy = "throw", onOverflow } = options;
|
|
4657
4716
|
const capacity = checkCapacity(options.capacity, "boundedDeque");
|
|
4658
4717
|
return (next) => {
|
|
4659
|
-
const
|
|
4718
|
+
const source = removalSource(next);
|
|
4719
|
+
const handleOverflow = (element, dropFrom) => {
|
|
4660
4720
|
if (onOverflow) {
|
|
4661
4721
|
const context = {
|
|
4662
4722
|
newElement: element,
|
|
@@ -4669,7 +4729,14 @@ function boundedDeque(options) {
|
|
|
4669
4729
|
switch (policy) {
|
|
4670
4730
|
case "throw": throw new Error(`Deque is full (capacity: ${capacity})`);
|
|
4671
4731
|
case "drop-oldest":
|
|
4672
|
-
if (!next.isEmpty)
|
|
4732
|
+
if (!next.isEmpty) {
|
|
4733
|
+
const value = dropFrom === "last" ? next.removeLast() : next.removeFirst();
|
|
4734
|
+
source.emit({
|
|
4735
|
+
value,
|
|
4736
|
+
end: dropFrom,
|
|
4737
|
+
reason: "drop"
|
|
4738
|
+
});
|
|
4739
|
+
}
|
|
4673
4740
|
break;
|
|
4674
4741
|
case "drop-newest":
|
|
4675
4742
|
case "reject": return;
|
|
@@ -4679,6 +4746,7 @@ function boundedDeque(options) {
|
|
|
4679
4746
|
}
|
|
4680
4747
|
};
|
|
4681
4748
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
4749
|
+
if (prop === removals) return source.channel;
|
|
4682
4750
|
if (prop === "capacity") return capacity;
|
|
4683
4751
|
if (prop === "isFull") return target.size >= capacity;
|
|
4684
4752
|
if (prop === "remainingCapacity") return Math.max(0, capacity - target.size);
|
|
@@ -4692,11 +4760,14 @@ function boundedDeque(options) {
|
|
|
4692
4760
|
case "offer":
|
|
4693
4761
|
case "add": return function(element) {
|
|
4694
4762
|
if (target.size >= capacity && policy !== "grow") {
|
|
4695
|
-
handleOverflow(element);
|
|
4763
|
+
handleOverflow(element, prop === "addFirst" || prop === "offerFirst" ? "last" : "first");
|
|
4696
4764
|
if (policy === "reject" || policy === "drop-newest") return prop.startsWith("offer") || prop === "add" ? false : void 0;
|
|
4697
4765
|
}
|
|
4698
4766
|
return value.call(target, element);
|
|
4699
4767
|
};
|
|
4768
|
+
case "addAll": return function(elements) {
|
|
4769
|
+
return addEach(elements, (element) => receiver.add(element));
|
|
4770
|
+
};
|
|
4700
4771
|
}
|
|
4701
4772
|
return value;
|
|
4702
4773
|
} });
|
|
@@ -4710,6 +4781,9 @@ function boundedMap(options) {
|
|
|
4710
4781
|
const capacity = checkCapacity(options.capacity, "boundedMap");
|
|
4711
4782
|
return (next) => {
|
|
4712
4783
|
let insertionOrder = [];
|
|
4784
|
+
const source = removalSource(next, ({ key }) => {
|
|
4785
|
+
insertionOrder = insertionOrder.filter((k) => !next.keyEq(k, key));
|
|
4786
|
+
});
|
|
4713
4787
|
const handleOverflow = (key, value) => {
|
|
4714
4788
|
if (onOverflow) onOverflow({
|
|
4715
4789
|
key,
|
|
@@ -4721,9 +4795,17 @@ function boundedMap(options) {
|
|
|
4721
4795
|
switch (policy) {
|
|
4722
4796
|
case "throw": throw new Error(`Map is full (capacity: ${capacity})`);
|
|
4723
4797
|
case "drop-oldest":
|
|
4724
|
-
|
|
4798
|
+
while (insertionOrder.length > 0) {
|
|
4725
4799
|
const oldestKey = insertionOrder.shift();
|
|
4800
|
+
if (!next.has(oldestKey)) continue;
|
|
4801
|
+
const value = next.get(oldestKey);
|
|
4726
4802
|
next.delete(oldestKey);
|
|
4803
|
+
source.emit({
|
|
4804
|
+
key: oldestKey,
|
|
4805
|
+
value,
|
|
4806
|
+
reason: "drop"
|
|
4807
|
+
});
|
|
4808
|
+
break;
|
|
4727
4809
|
}
|
|
4728
4810
|
break;
|
|
4729
4811
|
case "drop-newest":
|
|
@@ -4734,6 +4816,7 @@ function boundedMap(options) {
|
|
|
4734
4816
|
}
|
|
4735
4817
|
};
|
|
4736
4818
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
4819
|
+
if (prop === removals) return source.channel;
|
|
4737
4820
|
if (prop === "capacity") return capacity;
|
|
4738
4821
|
if (prop === "isFull") return target.size >= capacity;
|
|
4739
4822
|
if (prop === "remainingCapacity") return Math.max(0, capacity - target.size);
|
|
@@ -5063,6 +5146,24 @@ function safe() {
|
|
|
5063
5146
|
}
|
|
5064
5147
|
// src/behaviors/evented.ts
|
|
5065
5148
|
/**
|
|
5149
|
+
* This function gives each removal below `next` to `listener` and counts the removals.
|
|
5150
|
+
* With the counts, a layer tells a refused add (the size did not grow) from an add that caused a drop.
|
|
5151
|
+
*/
|
|
5152
|
+
function hear(next, listener) {
|
|
5153
|
+
const heard = {
|
|
5154
|
+
all: 0,
|
|
5155
|
+
first: 0
|
|
5156
|
+
};
|
|
5157
|
+
removalsOf(next)?.on((removal) => {
|
|
5158
|
+
heard.all++;
|
|
5159
|
+
if ("end" in removal && removal.end === "first") heard.first++;
|
|
5160
|
+
listener(removal);
|
|
5161
|
+
});
|
|
5162
|
+
return heard;
|
|
5163
|
+
}
|
|
5164
|
+
/** This function tells if a call added an element: the size grew, after the removals of the layers below. */
|
|
5165
|
+
const added = (size, sizeBefore, heard, heardBefore) => size - sizeBefore + heard.all - heardBefore > 0;
|
|
5166
|
+
/**
|
|
5066
5167
|
* This function makes an evented List middleware.
|
|
5067
5168
|
*
|
|
5068
5169
|
* This function emits events for add, remove, set, clear, sort, and reverse operations.
|
|
@@ -5070,28 +5171,44 @@ function safe() {
|
|
|
5070
5171
|
function eventedList() {
|
|
5071
5172
|
return (next) => {
|
|
5072
5173
|
const emitter = new Emitter();
|
|
5174
|
+
const heard = hear(next, ({ value, end, reason }) => emitter.emit("remove", end === void 0 ? {
|
|
5175
|
+
value,
|
|
5176
|
+
reason
|
|
5177
|
+
} : {
|
|
5178
|
+
value,
|
|
5179
|
+
index: end === "first" ? 0 : next.size,
|
|
5180
|
+
reason
|
|
5181
|
+
}));
|
|
5073
5182
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
5074
5183
|
if (prop === "on") return emitter.on.bind(emitter);
|
|
5075
5184
|
const value = Reflect.get(target, prop, receiver);
|
|
5076
5185
|
if (typeof value === "function") switch (prop) {
|
|
5077
5186
|
case "add":
|
|
5078
5187
|
case "push": return function(element) {
|
|
5188
|
+
const size = target.size;
|
|
5189
|
+
const all = heard.all;
|
|
5079
5190
|
const result = value.call(target, element);
|
|
5080
|
-
emitter.emit("add", { value: element });
|
|
5191
|
+
if (added(target.size, size, heard, all)) emitter.emit("add", { value: element });
|
|
5081
5192
|
return result;
|
|
5082
5193
|
};
|
|
5083
5194
|
case "unshift": return function(element) {
|
|
5195
|
+
const size = target.size;
|
|
5196
|
+
const all = heard.all;
|
|
5084
5197
|
value.call(target, element);
|
|
5085
|
-
emitter.emit("add", {
|
|
5198
|
+
if (added(target.size, size, heard, all)) emitter.emit("add", {
|
|
5086
5199
|
value: element,
|
|
5087
5200
|
index: 0
|
|
5088
5201
|
});
|
|
5089
5202
|
};
|
|
5090
5203
|
case "insert": return function(index, element) {
|
|
5204
|
+
const size = target.size;
|
|
5205
|
+
const all = heard.all;
|
|
5206
|
+
const first = heard.first;
|
|
5091
5207
|
value.call(target, index, element);
|
|
5208
|
+
if (!added(target.size, size, heard, all)) return;
|
|
5092
5209
|
emitter.emit("add", {
|
|
5093
5210
|
value: element,
|
|
5094
|
-
index
|
|
5211
|
+
index: heard.first > first ? Math.max(0, index - 1) : index
|
|
5095
5212
|
});
|
|
5096
5213
|
};
|
|
5097
5214
|
case "set": return function(index, element) {
|
|
@@ -5118,7 +5235,10 @@ function eventedList() {
|
|
|
5118
5235
|
};
|
|
5119
5236
|
case "pop": return function() {
|
|
5120
5237
|
const removed = value.call(target);
|
|
5121
|
-
emitter.emit("remove", {
|
|
5238
|
+
emitter.emit("remove", {
|
|
5239
|
+
value: removed,
|
|
5240
|
+
index: target.size
|
|
5241
|
+
});
|
|
5122
5242
|
return removed;
|
|
5123
5243
|
};
|
|
5124
5244
|
case "shift": return function() {
|
|
@@ -5152,6 +5272,10 @@ function eventedList() {
|
|
|
5152
5272
|
function eventedQueue() {
|
|
5153
5273
|
return (next) => {
|
|
5154
5274
|
const emitter = new Emitter();
|
|
5275
|
+
hear(next, ({ value, reason }) => emitter.emit("dequeue", {
|
|
5276
|
+
value,
|
|
5277
|
+
reason
|
|
5278
|
+
}));
|
|
5155
5279
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
5156
5280
|
if (prop === "on") return emitter.on.bind(emitter);
|
|
5157
5281
|
const value = Reflect.get(target, prop, receiver);
|
|
@@ -5184,23 +5308,25 @@ function eventedQueue() {
|
|
|
5184
5308
|
function eventedDeque() {
|
|
5185
5309
|
return (next) => {
|
|
5186
5310
|
const emitter = new Emitter();
|
|
5311
|
+
const heard = hear(next, ({ value, end, reason }) => emitter.emit(end === "last" ? "removeLast" : "removeFirst", {
|
|
5312
|
+
value,
|
|
5313
|
+
reason
|
|
5314
|
+
}));
|
|
5187
5315
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
5188
5316
|
if (prop === "on") return emitter.on.bind(emitter);
|
|
5189
5317
|
const value = Reflect.get(target, prop, receiver);
|
|
5190
5318
|
if (typeof value === "function") switch (prop) {
|
|
5191
5319
|
case "addFirst":
|
|
5192
5320
|
case "offerFirst":
|
|
5193
|
-
case "push": return function(element) {
|
|
5194
|
-
const result = value.call(target, element);
|
|
5195
|
-
emitter.emit("addFirst", { value: element });
|
|
5196
|
-
return result;
|
|
5197
|
-
};
|
|
5198
5321
|
case "addLast":
|
|
5199
5322
|
case "offerLast":
|
|
5200
5323
|
case "offer":
|
|
5201
|
-
case "add":
|
|
5324
|
+
case "add":
|
|
5325
|
+
case "push": return function(element) {
|
|
5326
|
+
const size = target.size;
|
|
5327
|
+
const all = heard.all;
|
|
5202
5328
|
const result = value.call(target, element);
|
|
5203
|
-
emitter.emit("addLast", { value: element });
|
|
5329
|
+
if (added(target.size, size, heard, all)) emitter.emit(prop === "addFirst" || prop === "offerFirst" ? "addFirst" : "addLast", { value: element });
|
|
5204
5330
|
return result;
|
|
5205
5331
|
};
|
|
5206
5332
|
case "removeFirst":
|
|
@@ -5233,13 +5359,20 @@ function eventedDeque() {
|
|
|
5233
5359
|
function eventedMap() {
|
|
5234
5360
|
return (next) => {
|
|
5235
5361
|
const emitter = new Emitter();
|
|
5362
|
+
const heard = hear(next, ({ key, value, reason }) => emitter.emit("delete", {
|
|
5363
|
+
key,
|
|
5364
|
+
value,
|
|
5365
|
+
reason
|
|
5366
|
+
}));
|
|
5236
5367
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
5237
5368
|
if (prop === "on") return emitter.on.bind(emitter);
|
|
5238
5369
|
const value = Reflect.get(target, prop, receiver);
|
|
5239
5370
|
if (typeof value === "function") switch (prop) {
|
|
5240
5371
|
case "set": return function(key, val) {
|
|
5372
|
+
const size = target.size;
|
|
5373
|
+
const all = heard.all;
|
|
5241
5374
|
const oldValue = value.call(target, key, val);
|
|
5242
|
-
emitter.emit("set", {
|
|
5375
|
+
if (oldValue !== void 0 || added(target.size, size, heard, all)) emitter.emit("set", {
|
|
5243
5376
|
key,
|
|
5244
5377
|
value: val,
|
|
5245
5378
|
oldValue
|
|
@@ -5269,6 +5402,10 @@ function eventedMap() {
|
|
|
5269
5402
|
function evented() {
|
|
5270
5403
|
return (next) => {
|
|
5271
5404
|
const emitter = new Emitter();
|
|
5405
|
+
hear(next, ({ value, reason }) => emitter.emit("remove", {
|
|
5406
|
+
value,
|
|
5407
|
+
reason
|
|
5408
|
+
}));
|
|
5272
5409
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
5273
5410
|
if (prop === "on") return emitter.on.bind(emitter);
|
|
5274
5411
|
const value = Reflect.get(target, prop, receiver);
|
|
@@ -5641,6 +5778,10 @@ function lruMap(options) {
|
|
|
5641
5778
|
else tail = node.prev;
|
|
5642
5779
|
nodeMap.delete(node.key);
|
|
5643
5780
|
};
|
|
5781
|
+
const source = removalSource(next, ({ key }) => {
|
|
5782
|
+
const node = nodeMap.get(key);
|
|
5783
|
+
if (node) removeNode(node);
|
|
5784
|
+
});
|
|
5644
5785
|
const dropStaleNodes = () => {
|
|
5645
5786
|
for (const node of [...nodeMap.values()]) if (!next.has(node.key)) removeNode(node);
|
|
5646
5787
|
};
|
|
@@ -5655,24 +5796,23 @@ function lruMap(options) {
|
|
|
5655
5796
|
value: candidate.value,
|
|
5656
5797
|
timestamp: candidate.lastAccessed
|
|
5657
5798
|
});
|
|
5799
|
+
source.emit({
|
|
5800
|
+
key: candidate.key,
|
|
5801
|
+
value: candidate.value,
|
|
5802
|
+
reason: "evict"
|
|
5803
|
+
});
|
|
5658
5804
|
return;
|
|
5659
5805
|
}
|
|
5660
5806
|
};
|
|
5661
5807
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
5808
|
+
if (prop === removals) return source.channel;
|
|
5662
5809
|
if (prop === "isFull") return target.size >= capacity;
|
|
5663
5810
|
const composite = compositeMapMethod(receiver, prop);
|
|
5664
5811
|
if (composite) return composite;
|
|
5665
5812
|
const value = Reflect.get(target, prop, receiver);
|
|
5666
5813
|
if (typeof value === "function") switch (prop) {
|
|
5667
5814
|
case "get": return function(key) {
|
|
5668
|
-
|
|
5669
|
-
try {
|
|
5670
|
-
result = value.call(target, key);
|
|
5671
|
-
} catch (error) {
|
|
5672
|
-
const stale = nodeMap.get(key);
|
|
5673
|
-
if (stale) removeNode(stale);
|
|
5674
|
-
throw error;
|
|
5675
|
-
}
|
|
5815
|
+
const result = value.call(target, key);
|
|
5676
5816
|
const node = nodeMap.get(key);
|
|
5677
5817
|
if (node) moveToHead(node);
|
|
5678
5818
|
return result;
|
|
@@ -5680,10 +5820,7 @@ function lruMap(options) {
|
|
|
5680
5820
|
case "has": return function(key) {
|
|
5681
5821
|
const result = value.call(target, key);
|
|
5682
5822
|
const node = nodeMap.get(key);
|
|
5683
|
-
if (node)
|
|
5684
|
-
if (result) moveToHead(node);
|
|
5685
|
-
else removeNode(node);
|
|
5686
|
-
}
|
|
5823
|
+
if (node && result) moveToHead(node);
|
|
5687
5824
|
return result;
|
|
5688
5825
|
};
|
|
5689
5826
|
case "set": return function(key, val) {
|
|
@@ -5698,7 +5835,7 @@ function lruMap(options) {
|
|
|
5698
5835
|
} else {
|
|
5699
5836
|
const stale = nodeMap.get(key);
|
|
5700
5837
|
if (stale) removeNode(stale);
|
|
5701
|
-
if (nodeMap.size >= capacity) dropStaleNodes();
|
|
5838
|
+
if (nodeMap.size >= 2 * capacity) dropStaleNodes();
|
|
5702
5839
|
if (target.size >= capacity) evictLRU();
|
|
5703
5840
|
const result = value.call(target, key, val);
|
|
5704
5841
|
addNode(key, val);
|
|
@@ -5886,33 +6023,40 @@ function startExpirationTimer(check, interval) {
|
|
|
5886
6023
|
/**
|
|
5887
6024
|
* This function makes a TTL Map middleware.
|
|
5888
6025
|
*
|
|
5889
|
-
*
|
|
5890
|
-
*
|
|
6026
|
+
* An entry is live while the time is less than the time of its last `set` plus `ttl`.
|
|
6027
|
+
* Each access to the map first removes the expired entries, so each read shows only live entries.
|
|
6028
|
+
* The layer gives each expired entry to `onExpire` one time, in the order of expiry.
|
|
6029
|
+
* A background interval also removes expired entries, so `onExpire` occurs with no access.
|
|
6030
|
+
*
|
|
6031
|
+
* The layer keeps the times in the order of expiry, so the removal before an access is amortized O(1).
|
|
6032
|
+
* This order needs a clock that does not go back. If `Date.now()` goes back, an entry can stay until the entries before it expire.
|
|
5891
6033
|
*/
|
|
5892
6034
|
function ttlMap(options) {
|
|
5893
6035
|
const { ttl, checkInterval = 1e3, onExpire } = options;
|
|
5894
6036
|
return (next) => {
|
|
5895
6037
|
const metadata = /* @__PURE__ */ new Map();
|
|
5896
6038
|
let intervalId = null;
|
|
5897
|
-
const
|
|
6039
|
+
const source = removalSource(next, ({ key }) => void metadata.delete(key));
|
|
6040
|
+
const sweep = () => {
|
|
5898
6041
|
const now = Date.now();
|
|
5899
|
-
const
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
6042
|
+
for (const [key, entry] of metadata) {
|
|
6043
|
+
if (now < entry.expiresAt) return;
|
|
6044
|
+
metadata.delete(key);
|
|
6045
|
+
next.delete(key);
|
|
6046
|
+
onExpire?.({
|
|
6047
|
+
key,
|
|
6048
|
+
value: entry.value,
|
|
6049
|
+
createdAt: entry.createdAt,
|
|
6050
|
+
expiredAt: now
|
|
6051
|
+
});
|
|
6052
|
+
source.emit({
|
|
6053
|
+
key,
|
|
6054
|
+
value: entry.value,
|
|
6055
|
+
reason: "expire"
|
|
6056
|
+
});
|
|
5913
6057
|
}
|
|
5914
6058
|
};
|
|
5915
|
-
intervalId = startExpirationTimer(
|
|
6059
|
+
intervalId = startExpirationTimer(sweep, checkInterval);
|
|
5916
6060
|
const dispose = () => {
|
|
5917
6061
|
if (intervalId) {
|
|
5918
6062
|
clearInterval(intervalId);
|
|
@@ -5921,31 +6065,18 @@ function ttlMap(options) {
|
|
|
5921
6065
|
};
|
|
5922
6066
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
5923
6067
|
if (prop === "dispose") return dispose;
|
|
6068
|
+
if (prop === removals) return source.channel;
|
|
5924
6069
|
const composite = compositeMapMethod(receiver, prop);
|
|
5925
6070
|
if (composite) return composite;
|
|
6071
|
+
sweep();
|
|
5926
6072
|
const value = Reflect.get(target, prop, receiver);
|
|
5927
|
-
if (typeof value
|
|
5928
|
-
|
|
5929
|
-
const entry = metadata.get(key);
|
|
5930
|
-
if (entry && Date.now() >= entry.expiresAt) {
|
|
5931
|
-
target.delete(key);
|
|
5932
|
-
metadata.delete(key);
|
|
5933
|
-
throw new Error(`Key expired: ${key}`);
|
|
5934
|
-
}
|
|
5935
|
-
return value.call(target, key);
|
|
5936
|
-
};
|
|
5937
|
-
case "has": return function(key) {
|
|
5938
|
-
const entry = metadata.get(key);
|
|
5939
|
-
if (entry && Date.now() >= entry.expiresAt) {
|
|
5940
|
-
target.delete(key);
|
|
5941
|
-
metadata.delete(key);
|
|
5942
|
-
return false;
|
|
5943
|
-
}
|
|
5944
|
-
return value.call(target, key);
|
|
5945
|
-
};
|
|
6073
|
+
if (typeof value !== "function") return value;
|
|
6074
|
+
switch (prop) {
|
|
5946
6075
|
case "set": return function(key, val) {
|
|
5947
|
-
|
|
6076
|
+
sweep();
|
|
5948
6077
|
const result = value.call(target, key, val);
|
|
6078
|
+
const now = Date.now();
|
|
6079
|
+
metadata.delete(key);
|
|
5949
6080
|
metadata.set(key, {
|
|
5950
6081
|
value: val,
|
|
5951
6082
|
expiresAt: now + ttl,
|
|
@@ -5954,16 +6085,36 @@ function ttlMap(options) {
|
|
|
5954
6085
|
return result;
|
|
5955
6086
|
};
|
|
5956
6087
|
case "delete": return function(key) {
|
|
5957
|
-
|
|
6088
|
+
sweep();
|
|
5958
6089
|
metadata.delete(key);
|
|
5959
|
-
return
|
|
6090
|
+
return value.call(target, key);
|
|
5960
6091
|
};
|
|
5961
6092
|
case "clear": return function() {
|
|
5962
|
-
value.call(target);
|
|
5963
6093
|
metadata.clear();
|
|
6094
|
+
value.call(target);
|
|
6095
|
+
};
|
|
6096
|
+
case Symbol.iterator:
|
|
6097
|
+
case "entries": return function() {
|
|
6098
|
+
sweep();
|
|
6099
|
+
return Array.from(target).values();
|
|
6100
|
+
};
|
|
6101
|
+
case "keys": return function() {
|
|
6102
|
+
sweep();
|
|
6103
|
+
return Array.from(target, (e) => e.key).values();
|
|
6104
|
+
};
|
|
6105
|
+
case "values": return function() {
|
|
6106
|
+
sweep();
|
|
6107
|
+
return Array.from(target, (e) => e.value).values();
|
|
6108
|
+
};
|
|
6109
|
+
case "forEach": return function(callback) {
|
|
6110
|
+
sweep();
|
|
6111
|
+
for (const e of Array.from(target)) callback(e.value, e.key, receiver);
|
|
6112
|
+
};
|
|
6113
|
+
default: return function(...args) {
|
|
6114
|
+
sweep();
|
|
6115
|
+
return value.apply(target, args);
|
|
5964
6116
|
};
|
|
5965
6117
|
}
|
|
5966
|
-
return value;
|
|
5967
6118
|
} });
|
|
5968
6119
|
};
|
|
5969
6120
|
}
|
|
@@ -5978,6 +6129,7 @@ function ttlCollection(options) {
|
|
|
5978
6129
|
return (next) => {
|
|
5979
6130
|
const metadata = /* @__PURE__ */ new Map();
|
|
5980
6131
|
let intervalId = null;
|
|
6132
|
+
const source = removalSource(next, ({ value }) => void metadata.delete(value));
|
|
5981
6133
|
const checkExpired = () => {
|
|
5982
6134
|
const now = Date.now();
|
|
5983
6135
|
const expired = [];
|
|
@@ -5992,6 +6144,10 @@ function ttlCollection(options) {
|
|
|
5992
6144
|
createdAt: entry.createdAt,
|
|
5993
6145
|
expiredAt: now
|
|
5994
6146
|
});
|
|
6147
|
+
source.emit({
|
|
6148
|
+
value: element,
|
|
6149
|
+
reason: "expire"
|
|
6150
|
+
});
|
|
5995
6151
|
}
|
|
5996
6152
|
}
|
|
5997
6153
|
};
|
|
@@ -6004,6 +6160,7 @@ function ttlCollection(options) {
|
|
|
6004
6160
|
};
|
|
6005
6161
|
return new Proxy(next, { get(target, prop, receiver) {
|
|
6006
6162
|
if (prop === "dispose") return dispose;
|
|
6163
|
+
if (prop === removals) return source.channel;
|
|
6007
6164
|
const value = Reflect.get(target, prop, receiver);
|
|
6008
6165
|
if (typeof value === "function") switch (prop) {
|
|
6009
6166
|
case "add": return function(element) {
|
|
@@ -7928,6 +8085,6 @@ function stringHashCode(str) {
|
|
|
7928
8085
|
}
|
|
7929
8086
|
return hash;
|
|
7930
8087
|
}
|
|
7931
|
-
export { ArrayDeque, ArrayList, AsyncEmitter, AsyncQueue, Channel, CollectionBuilder, DEFAULT_CONFLICT_POLICY, DEFAULT_EVICTION_POLICY, DEFAULT_GROWTH_POLICY, DEFAULT_OVERFLOW_POLICY, DEFAULT_RETRY_POLICY, DEFAULT_UNDERFLOW_POLICY, DEFAULT_VALIDATION_POLICY, Emitter, EventBatcher, HashMap, HashSet, KEY_DIGITS, LRUCache, LinkedHashMap, LinkedList, PriorityQueue, StorageListeners, SummaryTree, TTLCache, TreeMap, TreeSet, arrayContains, arrayDeque, arrayIterator, arrayList, arrayRemove, arrayRemoveAll, asSettled, asyncQueue, average, averagingNumber, blockMethods, booleanCompare, bounded, boundedDeque, boundedList, boundedMap, boundedQueue, buffered, buildList, buildMap, bundle, channel, checkBounds, checkInsertBounds, checkRangeBounds, chunk, clamp, collect, toArray as collectToArray, toList as collectToList, toMap as collectToMap, toSet as collectToSet, combineHashes, compareKeys, comparing, comparingKey, compose, concat, count, counting, createEmitter, dateCompare, deepEq, defaultCompare, defaultEq, defaultHash, delegate, distinct, distinctConsecutive, dropIndex, emptyList, emptyMap, emptyQueue, enhanceMethod, enumerate, eqBy, evented, eventedDeque, eventedList, eventedMap, eventedQueue, every, fanIn, fanOut, filter, filtering, find, findIndex, findLastIndex, first, flatMap, flatMapping, flatten, forwardEvent, groupBy, groupingBy, groupingByWith, hasCapability, hashBy, hashMap, hashNumber, hashSet, hashString, immutableListOf, immutableMapOf, interceptMethods, isValidKey, toMap$1 as iterToMap, joining, keyBetween, keysBetween, keysForDrop, last, linkedHashMap, linkedList, listOf, lruCache, lruMap, map, mapOf, mapping, max, maxBy, merge, min, minBy, mutableListOf, mutableMapOf, nCopies, none, nullsFirst, nullsLast, numberCompare, onWhen, partition, partitioningBy, pipe, pipeline, priorityQueue, queueOf, range, rangeTo, readWriteLock, readonly, readonlyCollection, readonlyList, readonlyMap, readonlyQueue, reduce, reducing, reducingWith, reentrantSynchronized, reportListenerError, requireNonEmpty, requireNonNull, reverse, reverseArrayIterator, reversed, safe, safeDeque, safeList, safeMap, safeQueue, scan, select, shallowEq, singletonList, singletonMap, skip, skipWhile, some, sort, stringCompare, stringCompareIgnoreCase, stringHashCode, sum, summarizingNumber, summaryTree, summingNumber, synchronized, synchronizedCollection, synchronizedList, synchronizedMap, synchronizedSet, take, takeWhile, tap, teeing, thenComparing, ticker, timeout, toArray$1 as toArray, toList$1 as toList, toMap$2 as toMap, toSet$1 as toSet, treeMap, treeSet, ttlCache, ttlCollection, ttlMap, unbuffered, unmodifiableCollection, unmodifiableList, unmodifiableMap, window, workerPool, zip };
|
|
8088
|
+
export { ArrayDeque, ArrayList, AsyncEmitter, AsyncQueue, Channel, CollectionBuilder, DEFAULT_CONFLICT_POLICY, DEFAULT_EVICTION_POLICY, DEFAULT_GROWTH_POLICY, DEFAULT_OVERFLOW_POLICY, DEFAULT_RETRY_POLICY, DEFAULT_UNDERFLOW_POLICY, DEFAULT_VALIDATION_POLICY, Emitter, EventBatcher, HashMap, HashSet, KEY_DIGITS, LRUCache, LinkedHashMap, LinkedList, PriorityQueue, StorageListeners, SummaryTree, TTLCache, TreeMap, TreeSet, arrayContains, arrayDeque, arrayIterator, arrayList, arrayRemove, arrayRemoveAll, asSettled, asyncQueue, average, averagingNumber, blockMethods, booleanCompare, bounded, boundedDeque, boundedList, boundedMap, boundedQueue, buffered, buildList, buildMap, bundle, channel, checkBounds, checkInsertBounds, checkRangeBounds, chunk, clamp, collect, toArray as collectToArray, toList as collectToList, toMap as collectToMap, toSet as collectToSet, combineHashes, compareKeys, comparing, comparingKey, compose, concat, count, counting, createEmitter, dateCompare, deepEq, defaultCompare, defaultEq, defaultHash, delegate, distinct, distinctConsecutive, dropIndex, emptyList, emptyMap, emptyQueue, enhanceMethod, enumerate, eqBy, evented, eventedDeque, eventedList, eventedMap, eventedQueue, every, fanIn, fanOut, filter, filtering, find, findIndex, findLastIndex, first, flatMap, flatMapping, flatten, forwardEvent, groupBy, groupingBy, groupingByWith, hasCapability, hashBy, hashMap, hashNumber, hashSet, hashString, immutableListOf, immutableMapOf, interceptMethods, isValidKey, toMap$1 as iterToMap, joining, keyBetween, keysBetween, keysForDrop, last, linkedHashMap, linkedList, listOf, lruCache, lruMap, map, mapOf, mapping, max, maxBy, merge, min, minBy, mutableListOf, mutableMapOf, nCopies, none, nullsFirst, nullsLast, numberCompare, onWhen, partition, partitioningBy, pipe, pipeline, priorityQueue, queueOf, range, rangeTo, readWriteLock, readonly, readonlyCollection, readonlyList, readonlyMap, readonlyQueue, reduce, reducing, reducingWith, reentrantSynchronized, removalSource, removals, removalsOf, reportListenerError, requireNonEmpty, requireNonNull, reverse, reverseArrayIterator, reversed, safe, safeDeque, safeList, safeMap, safeQueue, scan, select, shallowEq, singletonList, singletonMap, skip, skipWhile, some, sort, stringCompare, stringCompareIgnoreCase, stringHashCode, sum, summarizingNumber, summaryTree, summingNumber, synchronized, synchronizedCollection, synchronizedList, synchronizedMap, synchronizedSet, take, takeWhile, tap, teeing, thenComparing, ticker, timeout, toArray$1 as toArray, toList$1 as toList, toMap$2 as toMap, toSet$1 as toSet, treeMap, treeSet, ttlCache, ttlCollection, ttlMap, unbuffered, unmodifiableCollection, unmodifiableList, unmodifiableMap, window, workerPool, zip };
|
|
7932
8089
|
|
|
7933
8090
|
//# sourceMappingURL=index.js.map
|