@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/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
- if (dropFrom === "last") next.pop();
4527
- else next.shift();
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) next.poll();
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 handleOverflow = (element) => {
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) next.removeFirst();
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
- if (insertionOrder.length > 0) {
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", { value: removed });
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": return function(element) {
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
- let result;
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
- * Automatically removes entries after TTL expires.
5890
- * This function uses a background interval to check for expired entries.
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 checkExpired = () => {
6039
+ const source = removalSource(next, ({ key }) => void metadata.delete(key));
6040
+ const sweep = () => {
5898
6041
  const now = Date.now();
5899
- const expired = [];
5900
- for (const [key, entry] of metadata) if (now >= entry.expiresAt) expired.push(key);
5901
- for (const key of expired) {
5902
- const entry = metadata.get(key);
5903
- if (entry) {
5904
- next.delete(key);
5905
- metadata.delete(key);
5906
- if (onExpire) onExpire({
5907
- key,
5908
- value: entry.value,
5909
- createdAt: entry.createdAt,
5910
- expiredAt: now
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(checkExpired, checkInterval);
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 === "function") switch (prop) {
5928
- case "get": return function(key) {
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
- const now = Date.now();
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
- const result = value.call(target, key);
6088
+ sweep();
5958
6089
  metadata.delete(key);
5959
- return result;
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