@mark1russell7/collections 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The removal channel of a stack of layers (C23).
3
+ *
4
+ * Some layers remove entries or elements by themselves. The bounded layers drop one when they are full.
5
+ * `lruMap` evicts one, and `ttlMap` removes an expired one. A layer
6
+ * above them does not call these removals, so it cannot see them. The removal channel tells it.
7
+ *
8
+ * - Each removing layer gives its channel under the key `removals`. Its channel gives its own removals and the
9
+ * removals of the channel below it.
10
+ * - A layer that does not remove forwards the channel below it: a proxy gives each unknown key of its target.
11
+ * - `eventedMap` and the other evented layers emit an event for each removal of the channel below them.
12
+ *
13
+ * @example
14
+ * const cache = compose(eventedMap<string, number>(), lruMap({ capacity: 2 }))(hashMap());
15
+ * cache.on("delete", ({ key, reason }) => console.log(key, reason)); // an eviction gives the reason "evict"
16
+ */
17
+
18
+ import type { Listener, Unsubscribe } from "./events.js";
19
+
20
+ /** The key of the removal channel on a layer. */
21
+ export const removals: unique symbol = Symbol.for("@mark1russell7/collections/removals");
22
+
23
+ /**
24
+ * The cause of a removal: a bounded layer drops, `lruMap` evicts and `ttlMap` removes an expired entry.
25
+ */
26
+ export type RemovalReason = "drop" | "evict" | "expire";
27
+
28
+ /** A removal from a map: the key and the value that the layer removed. */
29
+ export interface MapRemoval<K, V> {
30
+ readonly key: K;
31
+ readonly value: V;
32
+ readonly reason: RemovalReason;
33
+ }
34
+
35
+ /**
36
+ * A removal of an element: the element and the end that it left. A removal from a collection with no ends,
37
+ * for example by `ttlCollection`, has no `end`.
38
+ */
39
+ export interface ElementRemoval<T> {
40
+ readonly value: T;
41
+ readonly end?: "first" | "last";
42
+ readonly reason: RemovalReason;
43
+ }
44
+
45
+ /** A channel of removals. The listener gets each removal after the collection changed. */
46
+ export interface RemovalChannel<R> {
47
+ on(listener: Listener<R>): Unsubscribe;
48
+ }
49
+
50
+ /** The channel of a removing layer, and the function that sends its own removals. */
51
+ export interface RemovalSource<R> {
52
+ readonly channel: RemovalChannel<R>;
53
+ emit(removal: R): void;
54
+ }
55
+
56
+ /**
57
+ * This function gives the removal channel of a collection, or `undefined` when no layer of it removes.
58
+ */
59
+ export function removalsOf<R>(collection: object): RemovalChannel<R> | undefined {
60
+ const channel: unknown = (collection as { readonly [removals]?: unknown })[removals];
61
+ if (typeof channel !== "object" || channel === null) return undefined;
62
+ return typeof (channel as { on?: unknown }).on === "function" ? (channel as RemovalChannel<R>) : undefined;
63
+ }
64
+
65
+ /**
66
+ * This function makes the channel of a removing layer over `next`.
67
+ * The channel forwards each removal below. It calls `below` first, so the layer corrects its own state first.
68
+ */
69
+ export function removalSource<R>(next: object, below?: (removal: R) => void): RemovalSource<R> {
70
+ const listeners = new Set<Listener<R>>();
71
+ const emit = (removal: R): void => {
72
+ for (const listener of [...listeners]) listener(removal);
73
+ };
74
+ removalsOf<R>(next)?.on((removal) => {
75
+ below?.(removal);
76
+ emit(removal);
77
+ });
78
+ const channel: RemovalChannel<R> = {
79
+ on(listener) {
80
+ listeners.add(listener);
81
+ return () => void listeners.delete(listener);
82
+ },
83
+ };
84
+ return { channel, emit };
85
+ }
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ export * from "./core/traits.js";
13
13
  export * from "./core/policies.js";
14
14
  export * from "./core/middleware.js";
15
15
  export * from "./core/events.js";
16
+ export * from "./core/removals.js";
16
17
  // ============================================================================
17
18
  // Interfaces
18
19
  // ============================================================================