Skip to content

Commit 9d880fc

Browse files
committed
imp: Properly implemented MapView & SetView classes.
1 parent 031c383 commit 9d880fc

File tree

17 files changed

+853
-318
lines changed

17 files changed

+853
-318
lines changed

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@byloth/core",
3-
"version": "2.0.2",
3+
"version": "2.1.0",
44
"description": "An unopinionated collection of useful functions and classes that I use widely in all my projects. 🔧",
55
"keywords": [
66
"Core",
@@ -62,14 +62,14 @@
6262
"devDependencies": {
6363
"@byloth/eslint-config-typescript": "^3.1.0",
6464
"@eslint/compat": "^1.2.8",
65-
"@types/node": "^22.14.1",
66-
"@vitest/coverage-v8": "^3.1.1",
67-
"eslint": "^9.25.0",
65+
"@types/node": "^22.15.2",
66+
"@vitest/coverage-v8": "^3.1.2",
67+
"eslint": "^9.25.1",
6868
"husky": "^9.1.7",
6969
"jsdom": "^26.1.0",
7070
"typescript": "^5.8.3",
71-
"vite": "^6.3.2",
72-
"vitest": "^3.1.1"
71+
"vite": "^6.3.3",
72+
"vitest": "^3.1.2"
7373
},
74-
"packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677"
74+
"packageManager": "pnpm@10.9.0+sha512.0486e394640d3c1fb3c9d43d49cf92879ff74f8516959c235308f5a8f62e2e19528a65cdc2a3058f587cde71eba3d5b56327c8c33a97e4c4051ca48a10ca2d5f"
7575
}

pnpm-lock.yaml

Lines changed: 266 additions & 265 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const VERSION = "2.0.2";
1+
export const VERSION = "2.1.0";
22

33
export type { Constructor, Interval, Timeout, ValueOf } from "./core/types.js";
44

@@ -20,6 +20,7 @@ export {
2020
GameLoop,
2121
JSONStorage,
2222
KeyException,
23+
MapView,
2324
NotImplementedException,
2425
NetworkException,
2526
PermissionException,
@@ -28,6 +29,7 @@ export {
2829
ReducedIterator,
2930
ReferenceException,
3031
RuntimeException,
32+
SetView,
3133
SmartIterator,
3234
SmartAsyncIterator,
3335
SmartPromise,
@@ -58,6 +60,7 @@ export type {
5860
KeyedIteratee,
5961
KeyedReducer,
6062
KeyedTypeGuardPredicate,
63+
MapViewEventsMap,
6164
MaybeAsyncKeyedIteratee,
6265
MaybeAsyncKeyedReducer,
6366
MaybeAsyncGeneratorFunction,
@@ -68,8 +71,11 @@ export type {
6871
PromiseExecutor,
6972
PromiseRejecter,
7073
PromiseResolver,
74+
ReadonlyMapView,
75+
ReadonlySetView,
7176
Reducer,
7277
RejectedHandler,
78+
SetViewEventsMap,
7379
TypeGuardPredicate
7480

7581
} from "./models/types.js";

src/models/callbacks/publisher.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export default class Publisher<T extends CallbackMap<T> = CallbackMap>
123123
}
124124

125125
/**
126-
* Subscribes a new subscriber to an event.
126+
* Subscribes to an event and adds a subscriber to be executed when the event is published.
127127
*
128128
* ---
129129
*
@@ -142,9 +142,9 @@ export default class Publisher<T extends CallbackMap<T> = CallbackMap>
142142
* @template K The key of the map containing the callback signature to subscribe.
143143
*
144144
* @param event The name of the event to subscribe to.
145-
* @param subscriber The subscriber to add to the event.
145+
* @param subscriber The subscriber to execute when the event is published.
146146
*
147-
* @returns A function that can be used to unsubscribe the subscriber.
147+
* @returns A function that can be used to unsubscribe the subscriber from the event.
148148
*/
149149
public subscribe<K extends keyof T>(event: K & string, subscriber: T[K]): () => void
150150
{
@@ -167,7 +167,7 @@ export default class Publisher<T extends CallbackMap<T> = CallbackMap>
167167
}
168168

169169
/**
170-
* Unsubscribes a subscriber from an event.
170+
* Unsubscribes from an event and removes a subscriber from being executed when the event is published.
171171
*
172172
* ---
173173
*

src/models/callbacks/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2+
import type Publisher from "./publisher.js";
3+
14
/**
25
* A type that represents a generic function.
36
*
@@ -24,7 +27,7 @@ export type Callback<A extends unknown[] = [], R = void> = (...args: A) => R;
2427
/**
2528
* An utility type that is required to represents a map of callbacks.
2629
*
27-
* It is used for type inheritance on the `Publisher` class signature.
30+
* It is used for type inheritance on the {@link Publisher} class signature.
2831
* Whenever you'll need to extend that class, you may need to use this type too.
2932
*
3033
* ---

src/models/collections/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import MapView from "./map-view.js";
2+
import SetView from "./set-view.js";
3+
4+
export { MapView, SetView };

src/models/collections/map-view.ts

Lines changed: 164 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,206 @@
11
import Publisher from "../callbacks/publisher.js";
2-
import type { MapViewEventsMap } from "./types.js";
32

4-
export interface ReadonlyMapView<K, V> extends ReadonlyMap<K, V>
5-
{
6-
subscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, callback: MapViewEventsMap<K, V>[T]): () => void;
7-
unsubscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, callback: MapViewEventsMap<K, V>[T]): void;
8-
}
3+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
4+
import type SetView from "./set-view.js";
5+
import type { MapViewEventsMap } from "./types.js";
96

10-
export class MapView<K, V> extends Map<K, V>
7+
/**
8+
* A wrapper class around the native {@link Map} class that provides additional functionality
9+
* for publishing events when entries are added, removed or the collection is cleared.
10+
* There's also a complementary class that works with the native `Set` class.
11+
* See also {@link SetView}.
12+
*
13+
* ---
14+
*
15+
* @example
16+
* ```ts
17+
* const map = new MapView<string, number>();
18+
*
19+
* map.subscribe("entry:add", (key: string, value: number) => console.log(`Added ${key}: ${value}`));
20+
* map.set("answer", 42); // Added answer: 42
21+
* ```
22+
*
23+
* ---
24+
*
25+
* @template K The type of the keys in the map.
26+
* @template V The type of the values in the map.
27+
*/
28+
export default class MapView<K, V> extends Map<K, V>
1129
{
30+
/**
31+
* The internal {@link Publisher} instance used to publish events.
32+
*/
1233
protected readonly _publisher: Publisher<MapViewEventsMap<K, V>>;
1334

35+
/**
36+
* Initializes a new instance of the {@link MapView} class.
37+
*
38+
* ---
39+
*
40+
* @example
41+
* ```ts
42+
* const map = new MapView<string, number>([["key1", 2], ["key2", 4], ["key3", 8]]);
43+
* ```
44+
*
45+
* ---
46+
*
47+
* @param iterable An optional iterable of key-value pairs to initialize the {@link Map} with.
48+
*/
1449
public constructor(iterable?: Iterable<[K, V]> | null)
1550
{
16-
super(iterable);
51+
super();
1752

1853
this._publisher = new Publisher();
54+
55+
if (iterable)
56+
{
57+
for (const [key, value] of iterable) { this.set(key, value); }
58+
}
1959
}
2060

61+
/**
62+
* Adds a new entry with a specified key and value to the {@link Map}.
63+
* If an entry with the same key already exists, the entry will be overwritten with the new value.
64+
*
65+
* ---
66+
*
67+
* @example
68+
* ```ts
69+
* const map = new MapView<string, number>();
70+
* map.set("key1", 2)
71+
* .set("key2", 4)
72+
* .set("key3", 8);
73+
*
74+
* console.log(map); // MapView { "key1" => 2, "key2" => 4, "key3" => 8 }
75+
* ```
76+
*
77+
* ---
78+
*
79+
* @param key The key of the entry to add.
80+
* @param value The value of the entry to add.
81+
*
82+
* @returns The current instance of the {@link MapView} class.
83+
*/
2184
public override set(key: K, value: V): this
2285
{
2386
super.set(key, value);
2487

25-
this._publisher.publish("key:set", key, value);
88+
this._publisher.publish("entry:add", key, value);
2689

2790
return this;
2891
}
92+
93+
/**
94+
* Removes an entry with a specified key from the {@link Map}.
95+
*
96+
* ---
97+
*
98+
* @example
99+
* ```ts
100+
* const map = new MapView<string, number>([["key1", 2], ["key2", 4], ["key3", 8]]);
101+
* map.delete("key2"); // true
102+
* map.delete("key4"); // false
103+
*
104+
* console.log(map); // MapView { "key1" => 2, "key3" => 8 }
105+
* ```
106+
*
107+
* ---
108+
*
109+
* @param key The key of the entry to remove.
110+
*
111+
* @returns `true` if the entry existed and has been removed; otherwise `false` if the entry doesn't exist.
112+
*/
29113
public override delete(key: K): boolean
30114
{
31115
const result = super.delete(key);
32-
if (result) { this._publisher.publish("key:delete", key); }
116+
if (result) { this._publisher.publish("entry:remove", key); }
33117

34118
return result;
35119
}
36120

121+
/**
122+
* Removes all entries from the {@link Map}.
123+
*
124+
* ---
125+
*
126+
* @example
127+
* ```ts
128+
* const map = new MapView<string, number>([["key1", 2], ["key2", 4], ["key3", 8]]);
129+
* map.clear();
130+
*
131+
* console.log(map); // MapView { }
132+
* ```
133+
*/
37134
public override clear(): void
38135
{
39136
const size = this.size;
40137

41138
super.clear();
42-
if (size > 0) { this._publisher.publish("map:clear"); }
139+
if (size > 0) { this._publisher.publish("collection:clear"); }
43140
}
44141

142+
/**
143+
* Subscribes to an event and adds a callback to be executed when the event is published.
144+
*
145+
* ---
146+
*
147+
* @example
148+
* ```ts
149+
* const map = new MapView<string, number>();
150+
* const unsubscribe = map.subscribe("entry:add", (key: string, value: number) =>
151+
* {
152+
* if (key === "answer") { unsubscribe(); }
153+
* console.log(`Added ${key}: ${value}`);
154+
* });
155+
*
156+
* map.set("key1", 2); // Added key1: 2
157+
* map.set("answer", 42); // Added answer: 42
158+
* map.set("key2", 4);
159+
* map.set("key3", 8);
160+
* ```
161+
*
162+
* ---
163+
*
164+
* @template T The key of the map containing the callback signature to subscribe.
165+
*
166+
* @param event The name of the event to subscribe to.
167+
* @param callback The callback to execute when the event is published.
168+
*
169+
* @returns A function that can be used to unsubscribe the callback from the event.
170+
*/
45171
public subscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, callback: MapViewEventsMap<K, V>[T]): () => void
46172
{
47173
return this._publisher.subscribe(event, callback);
48174
}
175+
176+
/**
177+
* Unsubscribes from an event and removes a callback from being executed when the event is published.
178+
*
179+
* ---
180+
*
181+
* @example
182+
* ```ts
183+
* const callback = (key: string, value: number) => console.log(`Added ${key}: ${value}`);
184+
* const map = new MapView<string, number>();
185+
*
186+
* map.subscribe("entry:add", callback);
187+
* map.set("key1", 2); // Added key1: 2
188+
*
189+
* map.unsubscribe("entry:add", callback);
190+
* map.set("key2", 4);
191+
* ```
192+
*
193+
* ---
194+
*
195+
* @template T The key of the map containing the callback signature to unsubscribe.
196+
*
197+
* @param event The name of the event to unsubscribe from.
198+
* @param callback The callback to remove from the event.
199+
*/
49200
public unsubscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, callback: MapViewEventsMap<K, V>[T]): void
50201
{
51202
this._publisher.unsubscribe(event, callback);
52203
}
204+
205+
public override readonly [Symbol.toStringTag]: string = "MapView";
53206
}

0 commit comments

Comments
 (0)