Skip to content

Commit 7cd4e08

Browse files
committed
misc
1 parent ae77767 commit 7cd4e08

File tree

5 files changed

+123
-97
lines changed

5 files changed

+123
-97
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "1.1.71",
2+
"version": "1.1.72",
33
"devDependencies": {
44
"@types/node": "^22.13.10",
55
"@types/ws": "^8.5.13",

src/listener.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export type NamedListeners<T extends Record<string, any>> = {
2323
any<K extends keyof T>(name: K, handler: (signal: AbortSignal) => void, signal?: AbortSignal);
2424

2525
/**
26-
* Synchronous check to see if anyone is listening to the give event.
26+
* Synchronous check to see if anyone is listening to the given event.
2727
*/
2828
hasAny<K extends keyof T>(name: K): boolean;
2929

src/maps.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* A set which allows the same value to be added many times.
33
*/
44
export class CountSet<T> {
5-
private m = new Map<T, number>();
5+
private readonly m = new Map<T, number>();
66
private count = 0;
77

88
/**
@@ -59,7 +59,7 @@ export class CountSet<T> {
5959
* `.add(b, a)`).
6060
*/
6161
export class PairSet<K> {
62-
private m = new PairMap<K, boolean>();
62+
private readonly m = new PairMap<K, boolean>();
6363

6464
size(): number {
6565
return this.m.size();
@@ -96,13 +96,17 @@ export class PairSet<K> {
9696
pairs(): IterableIterator<[K, K]> {
9797
return this.m.pairs();
9898
}
99+
100+
clear() {
101+
this.m.clear();
102+
}
99103
}
100104

101105
/**
102106
* A map with a pair of keys. Both sides are added at once.
103107
*/
104108
export class PairMap<K, V> {
105-
private m = new Map<K, Map<K, V>>();
109+
private readonly m = new Map<K, Map<K, V>>();
106110

107111
private implicitGet(k: K) {
108112
const has = this.m.get(k);
@@ -206,13 +210,17 @@ export class PairMap<K, V> {
206210
keys(): IterableIterator<K> {
207211
return this.m.keys();
208212
}
213+
214+
clear() {
215+
this.m.clear();
216+
}
209217
}
210218

211219
/**
212220
* A map which itself contains a set of items. Each key may have multiple items set.
213221
*/
214222
export class MultiMap<K, V> {
215-
private m = new Map<K, Set<V>>();
223+
private readonly m = new Map<K, Set<V>>();
216224
private _totalSize = 0;
217225

218226
add(k: K, v: V): boolean {
@@ -297,12 +305,16 @@ export class MultiMap<K, V> {
297305
keys(): Iterable<K> {
298306
return this.m.keys();
299307
}
308+
309+
clear() {
310+
this.m.clear();
311+
}
300312
}
301313

302314
export class TransformMap<K, V, T = V> {
303-
private data = new Map<K, V>();
304-
private defaultValue: V;
305-
private transform: (value: V, withValue: T) => V;
315+
private readonly data = new Map<K, V>();
316+
private readonly defaultValue: V;
317+
private readonly transform: (value: V, withValue: T) => V;
306318

307319
constructor(defaultValue: V, transform: (value: V, withValue: T) => V) {
308320
this.defaultValue = defaultValue;
@@ -354,4 +366,8 @@ export class TransformMap<K, V, T = V> {
354366
has(k: K): boolean {
355367
return this.data.has(k);
356368
}
369+
370+
clear() {
371+
this.data.clear();
372+
}
357373
}

src/select.ts

Lines changed: 62 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@ import { promiseWithResolvers } from './promise.ts';
22

33
const signalCache = new WeakMap<AbortSignal, Promise<void>>();
44

5-
type Fallback<T, F> = T extends undefined ? F : T;
6-
7-
type Entries<T> = {
8-
[K in keyof T]: [K, T[K]];
9-
}[keyof T][];
10-
115
/**
126
* Channel is borrowed from Go.
137
*
@@ -38,8 +32,7 @@ export type Channel<T> = {
3832
*
3933
* This is as {@link ReadChannel.wait}, but with a wider type.
4034
*/
41-
wait<V = Channel<T>>(): Promise<Fallback<V, Channel<T>>>;
42-
wait<V = Channel<T>>(value: V): Promise<Fallback<V, Channel<T>>>;
35+
wait<V = Channel<T>>(value?: V): Promise<V>;
4336
} & ReadChannel<T>;
4437

4538
export type ReadChannel<T> = {
@@ -56,137 +49,130 @@ export type ReadChannel<T> = {
5649
* Repeated calls to `wait()` will delay a microtask before resolving.
5750
* This allows a resolved task to safely consume a value synchronously after this resolves.
5851
*
59-
* For no other reason than convenience, this resolves with itself.
60-
* This isn't the value itself, the assumption is you can then synchronously consume the next value with {@link Channel#next}.
52+
* For no other reason than convenience, this resolves with itself, unless you include another value as an argument.
53+
* The assumption is you can then synchronously consume the next value with {@link ReadChannel#next}.
6154
*/
62-
wait<V = ReadChannel<T>>(): Promise<Fallback<V, ReadChannel<T>>>;
63-
wait<V = ReadChannel<T>>(value: V): Promise<Fallback<V, ReadChannel<T>>>;
55+
wait<V = ReadChannel<T>>(value?: V): Promise<V>;
6456

6557
/**
66-
* Returns whether {@link Channel#next} has an available value.
58+
* Returns whether {@link ReadChannel#next} has an available value.
6759
*/
6860
pending(): boolean;
6961

7062
/**
71-
* Consumes a value from this {@link Channel}, if possible.
63+
* Consumes a value from this {@link ReadChannel}, if possible.
7264
* Otherwise, returns `undefined`.
7365
*/
7466
next(): T | undefined;
7567
};
7668

7769
type MessageType<Q> = Q extends ReadChannel<infer X> ? X : never;
7870

79-
type SelectType<Q> = Q extends SelectRequest<infer X> ? X : never;
80-
81-
export type SelectRequest<T> = { [key: string | symbol]: ReadChannel<T> };
71+
export type SelectRequest = { [key: string | symbol]: ReadChannel<any> | undefined };
8272

83-
export type SelectResult<T extends SelectRequest<V>, V = SelectType<T>> = {
84-
[TKey in keyof T]: Readonly<{
73+
export type SelectResult<TChannels extends SelectRequest> = {
74+
[TKey in keyof TChannels]: Readonly<{
8575
key: TKey;
86-
ch: NonNullable<T[TKey]>;
87-
m: MessageType<T[TKey]>;
76+
ch: NonNullable<TChannels[TKey]>;
77+
m: MessageType<TChannels[TKey]>;
8878
closed: boolean;
8979
}>;
90-
}[keyof T];
91-
92-
export type SelectOption<T extends SelectRequest<V>, V = SelectType<T>> = {
93-
[TKey in keyof T]: Readonly<{
94-
key: TKey;
95-
ch: NonNullable<T[TKey]>;
96-
}>;
97-
}[keyof T];
80+
}[keyof TChannels];
9881

9982
/**
10083
* Waits for the first {@link Channel} that is ready based on key.
10184
* Returns with the key for matching.
10285
*
103-
* This uses JS' default object ordering: integers >= 0 in order, all others, symbols.
86+
* For safety, throws if no valid channels are passed (zero channels or all `undefined`).
87+
*
88+
* This uses JS' default object ordering for what is "first" when many are ready: integers >= 0 in order, all others, symbols.
10489
*/
105-
export function select<T extends SelectRequest<V>, V = SelectType<T>>(
106-
o: T,
107-
): Promise<SelectResult<T, V>>;
90+
export function select<TChannels extends SelectRequest>(
91+
o: TChannels,
92+
): Promise<SelectResult<TChannels>>;
10893

10994
/**
11095
* Waits for the first {@link Channel} that is ready based on key.
11196
* Returns with the key for matching.
11297
* Prefers the passed {@link AbortSignal}, which if aborted, returns `undefined`.
11398
*
114-
* This uses JS' default object ordering: integers >= 0 in order, all others, symbols.
99+
* Does not throw if missing channels, as the {@link AbortSignal} is an implied 'channel'.
100+
*
101+
* This uses JS' default object ordering for what is "first" when many are ready: integers >= 0 in order, all others, symbols.
115102
*/
116-
export function select<T extends SelectRequest<V>, V = SelectType<T>>(
117-
o: T,
103+
export function select<TChannels extends SelectRequest>(
104+
o: TChannels,
118105
signal: AbortSignal,
119-
): Promise<SelectResult<T, V> | undefined>;
106+
): Promise<SelectResult<TChannels> | undefined>;
107+
108+
export function select<TChannels extends SelectRequest>(
109+
o: TChannels,
110+
): Promise<SelectResult<TChannels>>;
120111

121-
export function select<T extends SelectRequest<V>, V = SelectType<T>>(
122-
o: T,
112+
export function select<TChannels extends SelectRequest>(
113+
o: TChannels,
123114
signal?: AbortSignal,
124-
): Promise<SelectResult<T, V> | undefined> {
115+
): Promise<SelectResult<TChannels> | undefined> {
125116
if (signal?.aborted) {
126117
return Promise.resolve().then(() => undefined);
127118
}
128119

129-
const sync = selectDefault<T, V>(o);
120+
const sync = selectDefault(o);
130121
if (sync !== undefined) {
131122
// nb. load-bearing extra Promise.resolve()
132123
return Promise.resolve().then(() => sync);
133124
}
134125

135126
// basically the key type of SelectResult
136-
const options: Promise<SelectOption<T, V> | undefined>[] = [];
127+
const options: Promise<void | { key: any; ch: ReadChannel<any>; m: any; closed: boolean }>[] = [];
137128

129+
let signalPromise: Promise<void> | undefined;
138130
if (signal !== undefined) {
139-
let signalPromise: Promise<void> | undefined = signalCache.get(signal);
131+
signalPromise = signalCache.get(signal);
140132
if (signalPromise === undefined) {
141133
signalPromise = new Promise((r) =>
142134
signal.addEventListener('abort', () => r(), { once: true }),
143135
);
144136
signalCache.set(signal, signalPromise);
145137
}
146-
options.push(signalPromise.then(() => undefined));
138+
options.push(signalPromise);
147139
}
148140

149-
// Assertion is needed to provide object entry key value pairs
150-
const entries = Object.entries(o) as Entries<T>;
151-
entries.forEach(([key, ch]) => {
141+
Object.entries(o).forEach(([key, ch]) => {
152142
if (ch) {
153-
options.push(ch.wait({ key, ch }));
143+
options.push(ch.wait({ key, ch, m: undefined, closed: false }));
154144
}
155145
});
146+
if (!options.length) {
147+
throw new Error(`select() without any valid channels`);
148+
}
149+
156150
const out = Promise.race(options)
157-
.then((choice): SelectResult<T, V> | undefined => {
151+
.then((choice) => {
158152
if (choice) {
159-
const { key, ch } = choice;
160-
return {
161-
key,
162-
ch,
163-
// wait() resolving implies that ch.next() will not return undefined
164-
m: choice.ch.next() as MessageType<T[keyof T]>,
165-
closed: choice.ch.closed,
166-
};
153+
choice.m = choice.ch.next();
154+
choice.closed = choice.ch.closed;
167155
}
168156
return choice;
169157
})
170158
// nb. load-bearing
171159
.then((x) => x);
172-
return out;
160+
return out as any;
173161
}
174162

175163
/**
176164
* Selects the first {@link ReadChannel} that is ready, or `undefined` if none are ready.
177165
* Returns with the key for matching.
178166
*
179-
* This uses JS' default object ordering: integers >= 0 in order, all others, symbols.
167+
* This uses JS' default object ordering for what is "first" when many are ready: integers >= 0 in order, all others, symbols.
180168
*/
181-
export function selectDefault<T extends SelectRequest<V>, V = SelectType<T>>(
182-
o: Partial<T>,
183-
): SelectResult<T, V> | undefined {
184-
for (const pendingKey of Reflect.ownKeys(o)) {
185-
// Assertion is needed to maintain generic type pairing
186-
const [key, ch] = [pendingKey, o[pendingKey]] as Entries<T>[number];
169+
export function selectDefault<
170+
TChannels extends { [key: string | symbol]: ReadChannel<any> | undefined },
171+
>(o: TChannels): SelectResult<TChannels> | undefined {
172+
for (const key of Reflect.ownKeys(o)) {
173+
const ch = o[key];
187174
if (ch?.pending()) {
188-
// pending() returning true implies that ch.next() will not return undefined
189-
return { key, ch, m: ch.next() as MessageType<T[keyof T]>, closed: ch.closed };
175+
return { key, ch: ch as any, m: ch.next(), closed: ch.closed };
190176
}
191177
}
192178
return undefined;
@@ -217,7 +203,7 @@ class ChannelImpl<T> implements Channel<T> {
217203
private tail: ChannelItem<T> | null = null;
218204

219205
private readonly waits: {
220-
promise: Promise<unknown>;
206+
promise: Promise<any>;
221207
resolve: () => void;
222208
}[] = [];
223209

@@ -242,12 +228,10 @@ class ChannelImpl<T> implements Channel<T> {
242228
});
243229
}
244230

245-
wait<V = Channel<T>>(value?: V): Promise<Fallback<V, Channel<T>>> {
246-
const pr = promiseWithResolvers<Fallback<V, Channel<T>>>();
247-
this.waits.push({
248-
promise: pr.promise,
249-
resolve: () => pr.resolve((value ?? this) as Fallback<V, Channel<T>>),
250-
});
231+
// awkward typing to allow automatic resolution with any other value
232+
wait<V = Channel<T>>(value: V = this as any): Promise<V> {
233+
const pr = promiseWithResolvers<V>();
234+
this.waits.push({ promise: pr.promise, resolve: () => pr.resolve(value) });
251235

252236
this.kickoffResolveTask();
253237

@@ -312,21 +296,11 @@ class ChannelImpl<T> implements Channel<T> {
312296
/**
313297
* Converts a {@link AbortSignal} into a channel which is closed when it aborts.
314298
*/
315-
export function channelForSignal<T = AbortSignal>(
316-
s: AbortSignal,
317-
): ReadChannel<Fallback<T, AbortSignal>>;
318-
export function channelForSignal<T = AbortSignal>(
319-
s: AbortSignal,
320-
v: T,
321-
): ReadChannel<Fallback<T, AbortSignal>>;
322-
export function channelForSignal<T = AbortSignal>(
323-
s: AbortSignal,
324-
v?: T,
325-
): ReadChannel<Fallback<T, AbortSignal>> {
299+
export function channelForSignal<T = AbortSignal>(s: AbortSignal, v: T = s as any): ReadChannel<T> {
326300
const o = {
327301
closed: false,
328302

329-
async wait<V = ReadChannel<T>>(value: V = o): Promise<V> {
303+
async wait<V = ReadChannel<T>>(value: V = o as any): Promise<V> {
330304
if (s.aborted) {
331305
return value;
332306
}
@@ -337,10 +311,10 @@ export function channelForSignal<T = AbortSignal>(
337311
return s.aborted;
338312
},
339313

340-
next(): Fallback<T, AbortSignal> | undefined {
314+
next(): T | undefined {
341315
if (s.aborted) {
342316
o.closed = true;
343-
return (v ?? s) as Fallback<T, AbortSignal>;
317+
return v;
344318
}
345319
return undefined;
346320
},

0 commit comments

Comments
 (0)