-
Notifications
You must be signed in to change notification settings - Fork 1
/
protocol.ts
190 lines (181 loc) · 7.29 KB
/
protocol.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/**
* Receiver allows callers to receive values.
* It uses a one-off callback that models what is going to receive the value.
*
* Unlike {@link Iterator}, it is not intended to support statefulness - a
* {@link Receivable} should return equivalent (but not necessarily identical)
* {@link Receiver} instances on each call to {@link getReceiver}.
*
* The {@link addReceiver} and {@link removeReceiver} methods are low-level
* constructs, and, in most scenarios, should not be called directly.
* When using these methods, consider the impact of cycles, particularly
* microtask cycles, and ways to mitigate them. See also
* {@link getYieldGeneration} and {@link yieldToMacrotaskQueue}.
*/
export type Receiver<T> = {
/**
* Add a receiver callback to a list of receivers, or call it immediately if
* there is an available sender.
* Returns true if the receiver was called added to the receiver list.
* Returns false if the receiver was called immediately.
*/
addReceiver: (callback: ReceiverCallback<T>) => boolean;
/**
* Immediately removes the receiver from the receiver list, if it is there.
*
* To facilitate "attempting synchronous receive", this method MUST only
* remove the _last_ matching occurrence of the callback, if it exists.
*/
removeReceiver: (callback: ReceiverCallback<T>) => void;
};
/**
* ReceiverCallback is a callback that receives a value from a sender and true,
* or a default value (or undefined if unsupported), and false, if the channel
* is closed.
*/
export type ReceiverCallback<T> = (
...[value, ok]: [T, true] | [T | undefined, false]
) => void;
/**
* Receivable is a value that can be converted to a {@link Receiver}.
*/
export type Receivable<T> = {
[getReceiver]: () => Receiver<T>;
};
/**
* See {@link Receivable}.
*/
export const getReceiver = Symbol('ts-chan.getReceiver');
/**
* Sender allows callers to send values.
* It uses a one-off callback that models what is going to send the value.
*
* Unlike {@link Iterator}, it is not intended to support statefulness - a
* {@link Sendable} should return equivalent (but not necessarily identical)
* {@link Sender} instances on each call to {@link getSender}.
*
* See also {@link SendOnClosedChannelError}, which SHOULD be raised on
* {@link addSender} (if closed on add) or passed into send callbacks
* (otherwise), when attempting to send on a closed channel.
*
* The {@link addSender} and {@link removeSender} methods are low-level
* constructs, and, in most scenarios, should not be called directly.
* When using these methods, consider the impact of cycles, particularly
* microtask cycles, and ways to mitigate them. See also
* {@link getYieldGeneration} and {@link yieldToMacrotaskQueue}.
*/
export type Sender<T> = {
/**
* Add a sender callback to a list of senders, or call it immediately if
* there is an available receiver.
* Returns true if the sender was added to the sender list.
* Returns false if the sender was called immediately.
* If the channel is closed, SHOULD throw {@link SendOnClosedChannelError}.
* If the channel is closed while the sender is waiting to be called, the
* sender SHOULD be called with {@link SendOnClosedChannelError}.
*/
addSender: (callback: SenderCallback<T>) => boolean;
/**
* Immediately removes the sender from the sender list, if it is there.
*
* To facilitate "attempting synchronous send", this method MUST only
* remove the _last_ matching occurrence of the callback, if it exists.
*/
removeSender: (callback: SenderCallback<T>) => void;
/**
* Closes the channel, adhering to the following semantics similar to Go's
* channels:
*
* - Once a channel is closed, no more values can be sent to it.
* - If a channel is buffered, and there are still values in the buffer when
* the channel is closed, the receivers will continue to receive those
* values until the buffer is empty.
* - It's the responsibility of the sender to close the channel, signaling to
* the receiver that no more data will be sent.
* - Attempting to send to a closed channel MUST result in an error, and
* MUST un-block any such senders as part of said close.
* - The error thrown when attempting to send on a closed channel SHOULD be
* {@link SendOnClosedChannelError}, but MAY be another error.
* - Unless explicitly documented as idempotent, `close` SHOULD throw
* {@link CloseOfClosedChannelError} on subsequent calls, but MAY throw
* other errors.
* - Channels should be closed to prevent potential deadlocks or to signal
* the end of data transmission. This ensures that receivers waiting on the
* channel don't do so indefinitely.
*
* Note: This method is optional. Some {@link Sendable} implementations may
* specify their own rules and semantics for closing channels. Always refer
* to the specific implementation's documentation to ensure correct usage and
* to prevent potential memory leaks or unexpected behaviors.
*
* See also {@link SendOnClosedChannelError} and
* {@link CloseOfClosedChannelError}.
*/
close?: () => void;
};
/**
* SenderCallback is called as a value is received, or when an error or some
* other event occurs, which prevents the value from being received.
* It accepts two parameters, an error (if any), and the boolean `ok`,
* indicating if the value has been (will be, after return) received.
* It MUST return the value (or throw) if `ok` is true, and SHOULD throw
* `err` if `ok` is false.
*
* The `ok` parameter being true guarantees that a value (once returned) has
* been received, though does not guarantee that anything will be done with it.
*
* If the `ok` parameter is false, the first parameter will contain any error,
* and no value (regardless of what is returned) will be received.
*
* Note: The sender callback is _not_ called on `removeSender`.
*
* WARNING: If the same value (===) as err (when ok is false) is thrown, that
* thrown error will not be bubbled - a mechanism used to avoid breaking the
* typing of the return value.
*/
export type SenderCallback<T> = (
...[err, ok]: [undefined, true] | [unknown, false]
) => T;
/**
* Sendable is a value that can be converted to a {@link Sender}.
*/
export type Sendable<T> = {
[getSender]: () => Sender<T>;
};
/**
* See {@link Sendable}.
*/
export const getSender = Symbol('ts-chan.getSender');
/**
* Provided as a convenience, that SHOULD be used by {@link Sender}
* implementations, to indicate that a channel is closed.
* Should be raised as a result of send attempts on a closed channel, where
* the send operation is not allowed to proceed.
*/
export class SendOnClosedChannelError extends Error {
constructor(...args: ConstructorParameters<typeof Error>) {
if (args.length === 0) {
args.length = 1;
}
if (args[0] === undefined) {
args[0] = 'ts-chan: send on closed channel';
}
super(...args);
}
}
/**
* Provided as a convenience, that SHOULD be used by {@link Sender}
* implementations, in the event that a channel close is attempted more than
* once.
*/
export class CloseOfClosedChannelError extends Error {
constructor(...args: ConstructorParameters<typeof Error>) {
if (args.length === 0) {
args.length = 1;
}
if (args[0] === undefined) {
args[0] = 'ts-chan: close of closed channel';
}
super(...args);
}
}