@thi.ng/csp 3.0.0 → 3.2.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/CHANGELOG.md +22 -1
- package/README.md +151 -16
- package/channel.d.ts +120 -2
- package/channel.js +116 -10
- package/mult.d.ts +48 -1
- package/mult.js +37 -1
- package/ops.d.ts +46 -2
- package/ops.js +10 -0
- package/package.json +2 -2
- package/pubsub.d.ts +40 -3
- package/pubsub.js +20 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2024-04-
|
|
3
|
+
- **Last updated**: 2024-04-28T14:28:17Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -9,6 +9,27 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
9
9
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
10
|
and/or version bumps of transitive dependencies.
|
|
11
11
|
|
|
12
|
+
## [3.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/csp@3.2.0) (2024-04-28)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- update Mult/PubSub unsub handling, add docs ([32ad70e](https://github.com/thi-ng/umbrella/commit/32ad70e))
|
|
17
|
+
- add optional auto-closing for Mult.unsubscribe(), PubSub.unsubscribeTopic()
|
|
18
|
+
- add docs
|
|
19
|
+
|
|
20
|
+
#### 🩹 Bug fixes
|
|
21
|
+
|
|
22
|
+
- update select() ([5e87c8d](https://github.com/thi-ng/umbrella/commit/5e87c8d))
|
|
23
|
+
- update select(), ensure write queue of selected channel is being updated
|
|
24
|
+
- mark Channel.updateQueue() as internal
|
|
25
|
+
|
|
26
|
+
## [3.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/csp@3.1.0) (2024-04-26)
|
|
27
|
+
|
|
28
|
+
#### 🚀 Features
|
|
29
|
+
|
|
30
|
+
- add opt. generics for PubSub.subscribe()/unsubscribe() ([b4e9d20](https://github.com/thi-ng/umbrella/commit/b4e9d20))
|
|
31
|
+
- add into() to feed (async) iterables into a channel ([c7c1f6d](https://github.com/thi-ng/umbrella/commit/c7c1f6d))
|
|
32
|
+
|
|
12
33
|
# [3.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/csp@3.0.0) (2024-04-25)
|
|
13
34
|
|
|
14
35
|
#### 🛑 Breaking changes
|
package/README.md
CHANGED
|
@@ -15,11 +15,18 @@
|
|
|
15
15
|
> GitHub](https://github.com/sponsors/postspectacular). Thank you! ❤️
|
|
16
16
|
|
|
17
17
|
- [About](#about)
|
|
18
|
+
- [What is CSP?](#what-is-csp)
|
|
19
|
+
- [Buffering behaviors](#buffering-behaviors)
|
|
20
|
+
- [Channels](#channels)
|
|
21
|
+
- [Other channel types](#other-channel-types)
|
|
22
|
+
- [Channel operators](#channel-operators)
|
|
18
23
|
- [Status](#status)
|
|
19
24
|
- [Related packages](#related-packages)
|
|
20
25
|
- [Installation](#installation)
|
|
21
26
|
- [Dependencies](#dependencies)
|
|
27
|
+
- [Usage examples](#usage-examples)
|
|
22
28
|
- [API](#api)
|
|
29
|
+
- [Ping pong](#ping-pong)
|
|
23
30
|
- [PubSub](#pubsub)
|
|
24
31
|
- [Authors](#authors)
|
|
25
32
|
- [License](#license)
|
|
@@ -29,25 +36,88 @@
|
|
|
29
36
|
Primitives & operators for Communicating Sequential Processes based on async/await and async iterables.
|
|
30
37
|
|
|
31
38
|
This package was temporarily deprecated (throughout most of 2023), but meanwhile
|
|
32
|
-
has been reanimated in the form of a
|
|
39
|
+
has been **reanimated in the form of a complete rewrite**, using a new, more
|
|
33
40
|
simple and more modern approach afforded by contemporary ES language features
|
|
34
41
|
(and widespread support for them).
|
|
35
42
|
|
|
36
43
|
**This new/current implementation is in most cases NOT compatible with earlier
|
|
37
44
|
versions**.
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
### What is CSP?
|
|
47
|
+
|
|
48
|
+
References:
|
|
49
|
+
|
|
50
|
+
- [Wikipedia](https://en.wikipedia.org/wiki/Communicating_sequential_processes)
|
|
51
|
+
- [Communicating Sequential Processes, C.A.R.
|
|
52
|
+
Hoare](https://dl.acm.org/doi/pdf/10.1145/359576.359585)
|
|
53
|
+
|
|
54
|
+
The key construct of this package is a read/write channel primitive which can be
|
|
55
|
+
customized with different buffer implementations to control blocking behaviors
|
|
56
|
+
and backpressure handling (aka attempting to write faster to a channel than
|
|
57
|
+
values are being read, essentially a memory management issue). Unbuffered CSP
|
|
58
|
+
channels are blocking on both the reader and writer side.
|
|
59
|
+
|
|
60
|
+
### Buffering behaviors
|
|
61
|
+
|
|
62
|
+
The following channel buffer types/behaviors are included (from the
|
|
63
|
+
[thi.ng/buffers](https://github.com/thi-ng/umbrella/tree/develop/packages/buffers)
|
|
64
|
+
package), all accepting a max. capacity and all implementing the
|
|
65
|
+
[IReadWriteBuffer](https://docs.thi.ng/umbrella/buffers/interfaces/IReadWriteBuffer.html)
|
|
66
|
+
interface required by the channel:
|
|
67
|
+
|
|
68
|
+
- [`fifo`](https://docs.thi.ng/umbrella/buffers/functions/fifo.html): First in,
|
|
69
|
+
first out ring buffer. Writes to the channel will start blocking once the
|
|
70
|
+
buffer's capacity is reached, otherwise complete immediately. Likewise,
|
|
71
|
+
channel reads are non-blocking whilst there're more buffered values available.
|
|
72
|
+
Reads will only block if the buffer is empty.
|
|
73
|
+
- [`lifo`](https://docs.thi.ng/umbrella/buffers/functions/lifo.html): Last in,
|
|
74
|
+
first out. Write behavior is the same as with `fifo`, reads are in reverse
|
|
75
|
+
order (as the name indicates), i.e. the last value written will be the first
|
|
76
|
+
value read (i.e. stack behavior).
|
|
77
|
+
- [`sliding`](https://docs.thi.ng/umbrella/buffers/functions/sliding.html):
|
|
78
|
+
Sliding window ring buffer. Writes to the channel are **never** blocking!
|
|
79
|
+
Whilst the buffer is at full capacity, new writes will first expunge the
|
|
80
|
+
oldest buffered value (similar to [LRU
|
|
81
|
+
cache](https://github.com/thi-ng/umbrella/blob/develop/packages/cache/README.md#lru)
|
|
82
|
+
behavior). Read behavior is the same as for `fifo`.
|
|
83
|
+
- [`dropping`](https://docs.thi.ng/umbrella/buffers/functions/dropping.html):
|
|
84
|
+
Dropping value ring buffer. Writes to the channel are **never** blocking!
|
|
85
|
+
Whilst the buffer is at full capacity, new writes will be silently ignored.
|
|
86
|
+
Read behavior is the same as for `fifo`.
|
|
87
|
+
|
|
88
|
+
### Channels
|
|
89
|
+
|
|
90
|
+
As mentioned previously,
|
|
91
|
+
[channels](https://docs.thi.ng/umbrella/csp/functions/channel.html) and their
|
|
92
|
+
[read](https://docs.thi.ng/umbrella/csp/classes/Channel.html#read),
|
|
93
|
+
[write](https://docs.thi.ng/umbrella/csp/classes/Channel.html#write) and
|
|
94
|
+
[close](https://docs.thi.ng/umbrella/csp/classes/Channel.html#close) operations
|
|
95
|
+
are the key building blocks for CSP.
|
|
96
|
+
|
|
97
|
+
### Other channel types
|
|
40
98
|
|
|
41
|
-
- [CSP `Channel`
|
|
42
|
-
primitive](https://docs.thi.ng/umbrella/csp/classes/Channel.html) supporting a
|
|
43
|
-
choice of buffer behaviors (fifo, sliding, dropping, see
|
|
44
|
-
[thi.ng/buffers](https://github.com/thi-ng/umbrella/blob/develop/packages/buffers)
|
|
45
|
-
for options)
|
|
46
|
-
- Composable channel operators (see list below)
|
|
47
99
|
- [`Mult`](https://docs.thi.ng/umbrella/csp/classes/Mult.html) for channel
|
|
48
|
-
multiplexing (one-to-many splitting) and dynamic add/removal of
|
|
100
|
+
multiplexing (aka one-to-many splitting) and dynamic add/removal of
|
|
101
|
+
subscribers
|
|
49
102
|
- [`PubSub`](https://docs.thi.ng/umbrella/csp/classes/PubSub.html) for
|
|
50
|
-
topic-based subscriptions, each topic implemented as `Mult`
|
|
103
|
+
topic-based subscriptions, each topic implemented as a `Mult`
|
|
104
|
+
|
|
105
|
+
### Channel operators
|
|
106
|
+
|
|
107
|
+
- [`broadcast()`](https://docs.thi.ng/umbrella/csp/functions/broadcast.html)
|
|
108
|
+
- [`channel()`](https://docs.thi.ng/umbrella/csp/functions/channel.html)
|
|
109
|
+
- [`concat()`](https://docs.thi.ng/umbrella/csp/functions/concat.html)
|
|
110
|
+
- [`consume()`](https://docs.thi.ng/umbrella/csp/functions/consume.html)
|
|
111
|
+
- [`consumeWith()`](https://docs.thi.ng/umbrella/csp/functions/consumeWith.html)
|
|
112
|
+
- [`drain()`](https://docs.thi.ng/umbrella/csp/functions/drain.html)
|
|
113
|
+
- [`fromAsyncIterable()`](https://docs.thi.ng/umbrella/csp/functions/fromAsyncIterable.html)
|
|
114
|
+
- [`into()`](https://docs.thi.ng/umbrella/csp/functions/into.html)
|
|
115
|
+
- [`merge()`](https://docs.thi.ng/umbrella/csp/functions/merge.html)
|
|
116
|
+
- [`mult()`](https://docs.thi.ng/umbrella/csp/functions/mult.html)
|
|
117
|
+
- [`pipe()`](https://docs.thi.ng/umbrella/csp/functions/pipe.html)
|
|
118
|
+
- [`pubsub()`](https://docs.thi.ng/umbrella/csp/functions/pubsub.html)
|
|
119
|
+
- [`select()`](https://docs.thi.ng/umbrella/csp/functions/select.html)
|
|
120
|
+
- [`timeout()`](https://docs.thi.ng/umbrella/csp/functions/timeout.html)
|
|
51
121
|
|
|
52
122
|
## Status
|
|
53
123
|
|
|
@@ -87,7 +157,7 @@ For Node.js REPL:
|
|
|
87
157
|
const csp = await import("@thi.ng/csp");
|
|
88
158
|
```
|
|
89
159
|
|
|
90
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 1.
|
|
160
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 1.79 KB
|
|
91
161
|
|
|
92
162
|
## Dependencies
|
|
93
163
|
|
|
@@ -96,14 +166,80 @@ Package sizes (brotli'd, pre-treeshake): ESM: 1.77 KB
|
|
|
96
166
|
- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
|
|
97
167
|
- [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
|
|
98
168
|
|
|
169
|
+
## Usage examples
|
|
170
|
+
|
|
171
|
+
One project in this repo's
|
|
172
|
+
[/examples](https://github.com/thi-ng/umbrella/tree/develop/examples)
|
|
173
|
+
directory is using this package:
|
|
174
|
+
|
|
175
|
+
| Screenshot | Description | Live demo | Source |
|
|
176
|
+
|:---------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------|:----------------------------------------------|:---------------------------------------------------------------------------|
|
|
177
|
+
| <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/csp-bus.png" width="240"/> | CSP channel-based event handling, async transducers & reactive UI components | [Demo](https://demo.thi.ng/umbrella/csp-bus/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/csp-bus) |
|
|
178
|
+
|
|
99
179
|
## API
|
|
100
180
|
|
|
101
181
|
[Generated API docs](https://docs.thi.ng/umbrella/csp/)
|
|
102
182
|
|
|
183
|
+
### Ping pong
|
|
184
|
+
|
|
185
|
+
```ts tangle:export/readme-pingpong.ts
|
|
186
|
+
import { channel } from "@thi.ng/csp";
|
|
187
|
+
|
|
188
|
+
// create CSP channel for bi-directional communication
|
|
189
|
+
const chan = channel<number>();
|
|
190
|
+
|
|
191
|
+
// create first async process (ping)
|
|
192
|
+
(async () => {
|
|
193
|
+
while (true) {
|
|
194
|
+
// this op will block until a value becomes available in the channel
|
|
195
|
+
const x = await chan.read();
|
|
196
|
+
// if the channel was closed meanwhile, read() will deliver `undefined`
|
|
197
|
+
if (x === undefined || x > 5) {
|
|
198
|
+
console.log("stopping...");
|
|
199
|
+
// calling close() is idempotent
|
|
200
|
+
// any in-flight writes will still be readable
|
|
201
|
+
chan.close();
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
console.log("ping", x);
|
|
205
|
+
// this op will also block until the other side is reading the value
|
|
206
|
+
await chan.write(x + 1);
|
|
207
|
+
}
|
|
208
|
+
console.log("ping done");
|
|
209
|
+
})();
|
|
210
|
+
|
|
211
|
+
// create second async process (pong, almost identical to ping)
|
|
212
|
+
(async () => {
|
|
213
|
+
while (true) {
|
|
214
|
+
// wait until value can be read (or channel closed)
|
|
215
|
+
const x = await chan.read();
|
|
216
|
+
// exit loop if channel closed
|
|
217
|
+
if (x === undefined) break;
|
|
218
|
+
console.log("pong", x);
|
|
219
|
+
// write next value & wait until other side read it
|
|
220
|
+
await chan.write(x + 1);
|
|
221
|
+
}
|
|
222
|
+
console.log("pong done");
|
|
223
|
+
})();
|
|
224
|
+
|
|
225
|
+
// kickoff
|
|
226
|
+
chan.write(0);
|
|
227
|
+
|
|
228
|
+
// ping 0
|
|
229
|
+
// pong 1
|
|
230
|
+
// ping 2
|
|
231
|
+
// pong 3
|
|
232
|
+
// ping 4
|
|
233
|
+
// pong 5
|
|
234
|
+
// stopping...
|
|
235
|
+
// ping done
|
|
236
|
+
// pong done
|
|
237
|
+
```
|
|
238
|
+
|
|
103
239
|
### PubSub
|
|
104
240
|
|
|
105
241
|
```ts tangle:export/readme-pubsub.ts
|
|
106
|
-
import { channel, consumeWith, pubsub } from "@thi.ng/csp";
|
|
242
|
+
import { channel, consumeWith, into, pubsub } from "@thi.ng/csp";
|
|
107
243
|
|
|
108
244
|
// input channel (optional)
|
|
109
245
|
const src = channel<string>({ id: "users" });
|
|
@@ -120,10 +256,9 @@ for (let i of "abc") {
|
|
|
120
256
|
consumeWith(pub.subscribeTopic(i), (x, ch) => console.log(ch.id, x));
|
|
121
257
|
}
|
|
122
258
|
|
|
123
|
-
// start processing
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
259
|
+
// start processing by feeding an iterable of names
|
|
260
|
+
await into(src, ["alice", "bert", "bella", "charlie", "arthur"]);
|
|
261
|
+
|
|
127
262
|
// users-a-tap0 alice
|
|
128
263
|
// users-b-tap1 bert
|
|
129
264
|
// users-b-tap1 bella
|
package/channel.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { Fn, Maybe } from "@thi.ng/api";
|
|
2
2
|
import type { ChannelBuffer, ChannelValue, IChannel } from "./api.js";
|
|
3
3
|
export declare const MAX_READS = 1024;
|
|
4
|
-
export declare const
|
|
4
|
+
export declare const MAX_WRITES = 1024;
|
|
5
5
|
export interface ChannelOpts {
|
|
6
6
|
id: string;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Syntax sugar for creating a new CSP {@link Channel}. By default, the channel
|
|
10
|
+
* has a buffer capacity of 1 value, but supports custom buffer sizes and/or
|
|
11
|
+
* implementations (described in readme).
|
|
12
|
+
*
|
|
13
|
+
* @param opts
|
|
14
|
+
*/
|
|
8
15
|
export declare function channel<T>(opts?: Partial<ChannelOpts>): Channel<T>;
|
|
9
16
|
export declare function channel<T>(buf: ChannelBuffer<T> | number, opts?: Partial<ChannelOpts>): Channel<T>;
|
|
10
17
|
export declare class Channel<T> implements IChannel<T> {
|
|
@@ -14,14 +21,119 @@ export declare class Channel<T> implements IChannel<T> {
|
|
|
14
21
|
reads: Fn<Maybe<T>, void>[];
|
|
15
22
|
races: Fn<Channel<T>, void>[];
|
|
16
23
|
protected state: number;
|
|
24
|
+
/**
|
|
25
|
+
* See {@link channel} for reference.
|
|
26
|
+
*
|
|
27
|
+
* @param opts
|
|
28
|
+
*/
|
|
17
29
|
constructor(opts?: Partial<ChannelOpts>);
|
|
18
30
|
constructor(buf: ChannelBuffer<T> | number, opts?: Partial<ChannelOpts>);
|
|
31
|
+
/**
|
|
32
|
+
* Returns an async iterator of this channel, acting as adapter between the
|
|
33
|
+
* CSP world and the more generic ES async iterables. The iterator stops
|
|
34
|
+
* once the channel has been closed and no further values can be read.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* Multiple iterators will compete for new values. To ensure an iterator
|
|
38
|
+
* receives all of the channel's values, you must either ensure there's only
|
|
39
|
+
* a single iterator per channel or feed the channel into a {@link mult}
|
|
40
|
+
* first and create an iterator of a channel obtained via
|
|
41
|
+
* {@link Mult.subscribe}.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts tangle:../export/channel-iterator.ts
|
|
45
|
+
* import { channel } from "@thi.ng/csp";
|
|
46
|
+
*
|
|
47
|
+
* const chan = channel<number>();
|
|
48
|
+
*
|
|
49
|
+
* (async () => {
|
|
50
|
+
* // implicit iterator conversion of the channel
|
|
51
|
+
* for await(let x of chan) console.log("received", x);
|
|
52
|
+
* console.log("channel closed");
|
|
53
|
+
* })()
|
|
54
|
+
*
|
|
55
|
+
* chan.write(1);
|
|
56
|
+
* chan.write(2);
|
|
57
|
+
* chan.write(3);
|
|
58
|
+
* chan.close();
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
19
61
|
[Symbol.asyncIterator](): AsyncIterableIterator<T>;
|
|
62
|
+
/**
|
|
63
|
+
* Attempts to read a value from the channel. The returned promise will
|
|
64
|
+
* block until such value becomes available or if the channel has been
|
|
65
|
+
* closed in the meantime. In that latter case, the promise will resolve to
|
|
66
|
+
* `undefined`.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* If a value is already available at the time of the function call, the
|
|
70
|
+
* promise resolves immediately.
|
|
71
|
+
*
|
|
72
|
+
* Note: There's a limit of in-flight {@link MAX_READS} allowed per channel.
|
|
73
|
+
* The promise will reject if that number is exceeded.
|
|
74
|
+
*
|
|
75
|
+
* Also see {@link Channel.poll}.
|
|
76
|
+
*/
|
|
20
77
|
read(): Promise<Maybe<T>>;
|
|
78
|
+
/**
|
|
79
|
+
* Similar to {@link Channel.read}, but not async and non-blocking. Will
|
|
80
|
+
* only succeed if the channel is readable (i.e. not yet closed) and if a
|
|
81
|
+
* value can be read immediately (without waiting). Returns the value or
|
|
82
|
+
* `undefined` if unsuccessful.
|
|
83
|
+
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* Use {@link Channel.closed} to check if the channel is already closed.
|
|
86
|
+
*/
|
|
21
87
|
poll(): Maybe<T>;
|
|
88
|
+
/**
|
|
89
|
+
* Attempts to write a new value to the channel and returns a promise
|
|
90
|
+
* indicating success or failure. Depending on buffer capacity & behavior,
|
|
91
|
+
* the returned promise will block until the channel accept new values (i.e.
|
|
92
|
+
* until the next {@link Channel.read}) or if it has been closed in the
|
|
93
|
+
* meantime. In that latter case, the promise will resolve to false.
|
|
94
|
+
*
|
|
95
|
+
* @remarks
|
|
96
|
+
* If the channel's buffer accepts new writes or if a read op is already
|
|
97
|
+
* waiting at the time of the function call, the promise resolves
|
|
98
|
+
* immediately.
|
|
99
|
+
*
|
|
100
|
+
* If the buffer is already full, the write will be queued and only resolve
|
|
101
|
+
* when delivered. Note: As a fail-safe, there's a limit of queued
|
|
102
|
+
* {@link MAX_WRITES} allowed per channel. The promise will reject if that
|
|
103
|
+
* number is exceeded.
|
|
104
|
+
*
|
|
105
|
+
* Also see {@link Channel.offer}.
|
|
106
|
+
*/
|
|
22
107
|
write(msg: T): Promise<boolean>;
|
|
108
|
+
/**
|
|
109
|
+
* Similar to {@link Channel.write}, but not async and non-blocking. Will
|
|
110
|
+
* only succeed if the channel is writable (i.e. not yet closed/closing) and
|
|
111
|
+
* if a write is immediately possible (without queuing). Returns true, if
|
|
112
|
+
* successful.
|
|
113
|
+
*
|
|
114
|
+
* @param msg
|
|
115
|
+
*/
|
|
23
116
|
offer(msg: T): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Queues a "race" operation & returns a promise which resolves with the
|
|
119
|
+
* channel itself when the channel becomes readable, but no other queued
|
|
120
|
+
* read operations are waiting (which always have priority). If the channel
|
|
121
|
+
* is already closed, the promise resolves immediately.
|
|
122
|
+
*
|
|
123
|
+
* @remarks
|
|
124
|
+
* This op is used internally by {@link select} to choose a channel to read
|
|
125
|
+
* from next.
|
|
126
|
+
*/
|
|
24
127
|
race(): Promise<Channel<T>>;
|
|
128
|
+
/**
|
|
129
|
+
* Triggers closing of the channel (idempotent). Any queued writes remain
|
|
130
|
+
* readable, but new writes will be ignored.
|
|
131
|
+
*
|
|
132
|
+
* @remarks
|
|
133
|
+
* Whilst there're still values available for reading,
|
|
134
|
+
* {@link Channel.closed} will still return false since the channel state is
|
|
135
|
+
* still "closing", not yet fully "closed".
|
|
136
|
+
*/
|
|
25
137
|
close(): void;
|
|
26
138
|
/**
|
|
27
139
|
* Returns true if the channel is principally readable (i.e. not yet
|
|
@@ -38,9 +150,15 @@ export declare class Channel<T> implements IChannel<T> {
|
|
|
38
150
|
/**
|
|
39
151
|
* Returns true if the channel is fully closed and no further reads or
|
|
40
152
|
* writes are possible.
|
|
153
|
+
*
|
|
154
|
+
* @remarks
|
|
155
|
+
* Whilst there're still values available for reading, this will still
|
|
156
|
+
* return false since the channel state is still "closing", not yet fully
|
|
157
|
+
* "closed".
|
|
41
158
|
*/
|
|
42
159
|
closed(): boolean;
|
|
160
|
+
/** @internal */
|
|
161
|
+
updateQueue(): void;
|
|
43
162
|
protected deliver(): void;
|
|
44
|
-
protected updateQueue(): void;
|
|
45
163
|
}
|
|
46
164
|
//# sourceMappingURL=channel.d.ts.map
|
package/channel.js
CHANGED
|
@@ -4,7 +4,7 @@ import { isPlainObject } from "@thi.ng/checks/is-plain-object";
|
|
|
4
4
|
import { illegalState } from "@thi.ng/errors/illegal-state";
|
|
5
5
|
import { __nextID } from "./idgen.js";
|
|
6
6
|
const MAX_READS = 1024;
|
|
7
|
-
const
|
|
7
|
+
const MAX_WRITES = 1024;
|
|
8
8
|
const STATE_OPEN = 0;
|
|
9
9
|
const STATE_CLOSING = 1;
|
|
10
10
|
const STATE_CLOSED = 2;
|
|
@@ -35,6 +35,36 @@ class Channel {
|
|
|
35
35
|
this.writes = isNumber(buf) ? fifo(buf) : buf;
|
|
36
36
|
this.id = opts?.id ?? `chan-${__nextID()}`;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Returns an async iterator of this channel, acting as adapter between the
|
|
40
|
+
* CSP world and the more generic ES async iterables. The iterator stops
|
|
41
|
+
* once the channel has been closed and no further values can be read.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* Multiple iterators will compete for new values. To ensure an iterator
|
|
45
|
+
* receives all of the channel's values, you must either ensure there's only
|
|
46
|
+
* a single iterator per channel or feed the channel into a {@link mult}
|
|
47
|
+
* first and create an iterator of a channel obtained via
|
|
48
|
+
* {@link Mult.subscribe}.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts tangle:../export/channel-iterator.ts
|
|
52
|
+
* import { channel } from "@thi.ng/csp";
|
|
53
|
+
*
|
|
54
|
+
* const chan = channel<number>();
|
|
55
|
+
*
|
|
56
|
+
* (async () => {
|
|
57
|
+
* // implicit iterator conversion of the channel
|
|
58
|
+
* for await(let x of chan) console.log("received", x);
|
|
59
|
+
* console.log("channel closed");
|
|
60
|
+
* })()
|
|
61
|
+
*
|
|
62
|
+
* chan.write(1);
|
|
63
|
+
* chan.write(2);
|
|
64
|
+
* chan.write(3);
|
|
65
|
+
* chan.close();
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
38
68
|
async *[Symbol.asyncIterator]() {
|
|
39
69
|
while (this.state < STATE_CLOSED) {
|
|
40
70
|
const x = await this.read();
|
|
@@ -42,6 +72,21 @@ class Channel {
|
|
|
42
72
|
yield x;
|
|
43
73
|
}
|
|
44
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Attempts to read a value from the channel. The returned promise will
|
|
77
|
+
* block until such value becomes available or if the channel has been
|
|
78
|
+
* closed in the meantime. In that latter case, the promise will resolve to
|
|
79
|
+
* `undefined`.
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* If a value is already available at the time of the function call, the
|
|
83
|
+
* promise resolves immediately.
|
|
84
|
+
*
|
|
85
|
+
* Note: There's a limit of in-flight {@link MAX_READS} allowed per channel.
|
|
86
|
+
* The promise will reject if that number is exceeded.
|
|
87
|
+
*
|
|
88
|
+
* Also see {@link Channel.poll}.
|
|
89
|
+
*/
|
|
45
90
|
read() {
|
|
46
91
|
return new Promise((resolve) => {
|
|
47
92
|
if (!this.readable()) {
|
|
@@ -59,6 +104,15 @@ class Channel {
|
|
|
59
104
|
this.deliver();
|
|
60
105
|
});
|
|
61
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Similar to {@link Channel.read}, but not async and non-blocking. Will
|
|
109
|
+
* only succeed if the channel is readable (i.e. not yet closed) and if a
|
|
110
|
+
* value can be read immediately (without waiting). Returns the value or
|
|
111
|
+
* `undefined` if unsuccessful.
|
|
112
|
+
*
|
|
113
|
+
* @remarks
|
|
114
|
+
* Use {@link Channel.closed} to check if the channel is already closed.
|
|
115
|
+
*/
|
|
62
116
|
poll() {
|
|
63
117
|
const { reads, writes } = this;
|
|
64
118
|
if (this.readable() && !reads.length && writes.readable()) {
|
|
@@ -68,6 +122,25 @@ class Channel {
|
|
|
68
122
|
return msg;
|
|
69
123
|
}
|
|
70
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Attempts to write a new value to the channel and returns a promise
|
|
127
|
+
* indicating success or failure. Depending on buffer capacity & behavior,
|
|
128
|
+
* the returned promise will block until the channel accept new values (i.e.
|
|
129
|
+
* until the next {@link Channel.read}) or if it has been closed in the
|
|
130
|
+
* meantime. In that latter case, the promise will resolve to false.
|
|
131
|
+
*
|
|
132
|
+
* @remarks
|
|
133
|
+
* If the channel's buffer accepts new writes or if a read op is already
|
|
134
|
+
* waiting at the time of the function call, the promise resolves
|
|
135
|
+
* immediately.
|
|
136
|
+
*
|
|
137
|
+
* If the buffer is already full, the write will be queued and only resolve
|
|
138
|
+
* when delivered. Note: As a fail-safe, there's a limit of queued
|
|
139
|
+
* {@link MAX_WRITES} allowed per channel. The promise will reject if that
|
|
140
|
+
* number is exceeded.
|
|
141
|
+
*
|
|
142
|
+
* Also see {@link Channel.offer}.
|
|
143
|
+
*/
|
|
71
144
|
write(msg) {
|
|
72
145
|
return new Promise((resolve) => {
|
|
73
146
|
if (!this.writable()) {
|
|
@@ -77,7 +150,7 @@ class Channel {
|
|
|
77
150
|
const { reads, writes, races, queue } = this;
|
|
78
151
|
const val = [msg, resolve];
|
|
79
152
|
if (!(writes.writable() && writes.write(val))) {
|
|
80
|
-
queue.length <
|
|
153
|
+
queue.length < MAX_WRITES ? queue.push(val) : illegalState(
|
|
81
154
|
"max. queue capacity reached, reduce back pressure!"
|
|
82
155
|
);
|
|
83
156
|
}
|
|
@@ -88,6 +161,14 @@ class Channel {
|
|
|
88
161
|
}
|
|
89
162
|
});
|
|
90
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Similar to {@link Channel.write}, but not async and non-blocking. Will
|
|
166
|
+
* only succeed if the channel is writable (i.e. not yet closed/closing) and
|
|
167
|
+
* if a write is immediately possible (without queuing). Returns true, if
|
|
168
|
+
* successful.
|
|
169
|
+
*
|
|
170
|
+
* @param msg
|
|
171
|
+
*/
|
|
91
172
|
offer(msg) {
|
|
92
173
|
if (this.writable() && this.writes.writable()) {
|
|
93
174
|
this.write(msg);
|
|
@@ -95,6 +176,16 @@ class Channel {
|
|
|
95
176
|
}
|
|
96
177
|
return false;
|
|
97
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Queues a "race" operation & returns a promise which resolves with the
|
|
181
|
+
* channel itself when the channel becomes readable, but no other queued
|
|
182
|
+
* read operations are waiting (which always have priority). If the channel
|
|
183
|
+
* is already closed, the promise resolves immediately.
|
|
184
|
+
*
|
|
185
|
+
* @remarks
|
|
186
|
+
* This op is used internally by {@link select} to choose a channel to read
|
|
187
|
+
* from next.
|
|
188
|
+
*/
|
|
98
189
|
race() {
|
|
99
190
|
return new Promise((resolve) => {
|
|
100
191
|
if (!this.readable()) {
|
|
@@ -107,6 +198,15 @@ class Channel {
|
|
|
107
198
|
}
|
|
108
199
|
});
|
|
109
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Triggers closing of the channel (idempotent). Any queued writes remain
|
|
203
|
+
* readable, but new writes will be ignored.
|
|
204
|
+
*
|
|
205
|
+
* @remarks
|
|
206
|
+
* Whilst there're still values available for reading,
|
|
207
|
+
* {@link Channel.closed} will still return false since the channel state is
|
|
208
|
+
* still "closing", not yet fully "closed".
|
|
209
|
+
*/
|
|
110
210
|
close() {
|
|
111
211
|
if (this.state >= STATE_CLOSING)
|
|
112
212
|
return;
|
|
@@ -141,17 +241,16 @@ class Channel {
|
|
|
141
241
|
/**
|
|
142
242
|
* Returns true if the channel is fully closed and no further reads or
|
|
143
243
|
* writes are possible.
|
|
244
|
+
*
|
|
245
|
+
* @remarks
|
|
246
|
+
* Whilst there're still values available for reading, this will still
|
|
247
|
+
* return false since the channel state is still "closing", not yet fully
|
|
248
|
+
* "closed".
|
|
144
249
|
*/
|
|
145
250
|
closed() {
|
|
146
251
|
return this.state === STATE_CLOSED;
|
|
147
252
|
}
|
|
148
|
-
|
|
149
|
-
const { reads, writes } = this;
|
|
150
|
-
const [msg, write] = writes.read();
|
|
151
|
-
write(true);
|
|
152
|
-
reads.shift()(msg);
|
|
153
|
-
this.updateQueue();
|
|
154
|
-
}
|
|
253
|
+
/** @internal */
|
|
155
254
|
updateQueue() {
|
|
156
255
|
const { queue, writes } = this;
|
|
157
256
|
if (queue.length && writes.writable()) {
|
|
@@ -161,10 +260,17 @@ class Channel {
|
|
|
161
260
|
this.state = STATE_CLOSED;
|
|
162
261
|
}
|
|
163
262
|
}
|
|
263
|
+
deliver() {
|
|
264
|
+
const { reads, writes } = this;
|
|
265
|
+
const [msg, write] = writes.read();
|
|
266
|
+
write(true);
|
|
267
|
+
reads.shift()(msg);
|
|
268
|
+
this.updateQueue();
|
|
269
|
+
}
|
|
164
270
|
}
|
|
165
271
|
export {
|
|
166
272
|
Channel,
|
|
167
|
-
MAX_QUEUE,
|
|
168
273
|
MAX_READS,
|
|
274
|
+
MAX_WRITES,
|
|
169
275
|
channel
|
|
170
276
|
};
|
package/mult.d.ts
CHANGED
|
@@ -1,16 +1,63 @@
|
|
|
1
1
|
import type { IClosable, IWriteable } from "./api.js";
|
|
2
2
|
import { Channel } from "./channel.js";
|
|
3
|
+
/**
|
|
4
|
+
* Syntax sugar for {@link Mult} ctor. Creates a new `Mult` which allows
|
|
5
|
+
* multiple child subscriptions, each receiving the same values written to (or
|
|
6
|
+
* received by) the Mult itself, i.e. it acts as a channel splitter, supporting
|
|
7
|
+
* dynamic subscriptions and unsubscriptions.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* If `src` is a channel, it will be used as input. If given a string, a new
|
|
11
|
+
* channel with the given ID will be created (for receiving values).
|
|
12
|
+
*
|
|
13
|
+
* @param arg
|
|
14
|
+
*/
|
|
3
15
|
export declare const mult: <T>(arg?: string | Channel<T>) => Mult<T>;
|
|
4
16
|
export declare class Mult<T> implements IWriteable<T>, IClosable {
|
|
5
17
|
protected src: Channel<any>;
|
|
6
18
|
protected taps: Channel<any>[];
|
|
19
|
+
/**
|
|
20
|
+
* See {@link mult} for reference.
|
|
21
|
+
*
|
|
22
|
+
* @param arg
|
|
23
|
+
*/
|
|
7
24
|
constructor(arg?: string | Channel<T>);
|
|
8
25
|
writable(): boolean;
|
|
9
26
|
write(val: T): Promise<boolean>;
|
|
10
27
|
close(): void;
|
|
11
28
|
closed(): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Attaches (and possibly creates) a new subscription channel to receive any
|
|
31
|
+
* values received by the `Mult` itself. Returns it.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* The channel can later be detached again via {@link Mult.unsubscribe}.
|
|
35
|
+
*
|
|
36
|
+
* @param ch
|
|
37
|
+
*/
|
|
12
38
|
subscribe(ch?: Channel<T>): Channel<T>;
|
|
13
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Attempts to remove given subscription channel. Returns true if
|
|
41
|
+
* successful. If `close` is true (default), the given channel will also be
|
|
42
|
+
* closed (only if unsubscription was successful).
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* See {@link Mult.subscribe} for reverse op.
|
|
46
|
+
*
|
|
47
|
+
* @param ch
|
|
48
|
+
* @param close
|
|
49
|
+
*/
|
|
50
|
+
unsubscribe(ch: Channel<T>, close?: boolean): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Removes all child subscription channels and if `close` is true (default)
|
|
53
|
+
* also closes them.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* The `Mult` itself will remain active and will continue to accept new
|
|
57
|
+
* subscriptions.
|
|
58
|
+
*
|
|
59
|
+
* @param close
|
|
60
|
+
*/
|
|
14
61
|
unsubscribeAll(close?: boolean): void;
|
|
15
62
|
protected process(): Promise<void>;
|
|
16
63
|
}
|
package/mult.js
CHANGED
|
@@ -4,6 +4,11 @@ const mult = (arg) => new Mult(arg);
|
|
|
4
4
|
class Mult {
|
|
5
5
|
src;
|
|
6
6
|
taps = [];
|
|
7
|
+
/**
|
|
8
|
+
* See {@link mult} for reference.
|
|
9
|
+
*
|
|
10
|
+
* @param arg
|
|
11
|
+
*/
|
|
7
12
|
constructor(arg) {
|
|
8
13
|
let id, src;
|
|
9
14
|
if (typeof arg === "string") {
|
|
@@ -26,6 +31,15 @@ class Mult {
|
|
|
26
31
|
closed() {
|
|
27
32
|
return this.src.closed();
|
|
28
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Attaches (and possibly creates) a new subscription channel to receive any
|
|
36
|
+
* values received by the `Mult` itself. Returns it.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* The channel can later be detached again via {@link Mult.unsubscribe}.
|
|
40
|
+
*
|
|
41
|
+
* @param ch
|
|
42
|
+
*/
|
|
29
43
|
subscribe(ch) {
|
|
30
44
|
if (!ch) {
|
|
31
45
|
ch = new Channel({
|
|
@@ -37,14 +51,36 @@ class Mult {
|
|
|
37
51
|
this.taps.push(ch);
|
|
38
52
|
return ch;
|
|
39
53
|
}
|
|
40
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Attempts to remove given subscription channel. Returns true if
|
|
56
|
+
* successful. If `close` is true (default), the given channel will also be
|
|
57
|
+
* closed (only if unsubscription was successful).
|
|
58
|
+
*
|
|
59
|
+
* @remarks
|
|
60
|
+
* See {@link Mult.subscribe} for reverse op.
|
|
61
|
+
*
|
|
62
|
+
* @param ch
|
|
63
|
+
* @param close
|
|
64
|
+
*/
|
|
65
|
+
unsubscribe(ch, close = true) {
|
|
41
66
|
const idx = this.taps.indexOf(ch);
|
|
42
67
|
if (idx >= 0) {
|
|
43
68
|
this.taps.splice(idx, 1);
|
|
69
|
+
close && ch.close();
|
|
44
70
|
return true;
|
|
45
71
|
}
|
|
46
72
|
return false;
|
|
47
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Removes all child subscription channels and if `close` is true (default)
|
|
76
|
+
* also closes them.
|
|
77
|
+
*
|
|
78
|
+
* @remarks
|
|
79
|
+
* The `Mult` itself will remain active and will continue to accept new
|
|
80
|
+
* subscriptions.
|
|
81
|
+
*
|
|
82
|
+
* @param close
|
|
83
|
+
*/
|
|
48
84
|
unsubscribeAll(close = true) {
|
|
49
85
|
if (close) {
|
|
50
86
|
for (let t of this.taps)
|
package/ops.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { Channel } from "./channel.js";
|
|
|
4
4
|
export declare const broadcast: <T>(src: Channel<T>, dest: Channel<T>[], close?: boolean) => Promise<void>;
|
|
5
5
|
export declare const concat: <T>(dest: IWriteable<T> & IClosable, chans: Iterable<Channel<T>>, close?: boolean) => Promise<void>;
|
|
6
6
|
/**
|
|
7
|
-
* Consumes & collects all future values written to channel `chan`
|
|
8
|
-
* closed or the max. number of values has been collected (whatever comes
|
|
7
|
+
* Consumes & collects all queued and future values written to channel `chan`
|
|
8
|
+
* until closed or the max. number of values has been collected (whatever comes
|
|
9
9
|
* first). Returns a promise of the result array.
|
|
10
10
|
*
|
|
11
11
|
* @param chan
|
|
@@ -13,11 +13,55 @@ export declare const concat: <T>(dest: IWriteable<T> & IClosable, chans: Iterabl
|
|
|
13
13
|
* @param num
|
|
14
14
|
*/
|
|
15
15
|
export declare const consume: <T>(chan: Channel<T>, res?: T[], num?: number) => Promise<T[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Consumes all queued and future values written to channel `chan` until closed
|
|
18
|
+
* or the max. number of values has been reached (whatever comes first). Calls
|
|
19
|
+
* `fn` with each value read (presumably for side effects). Returns a void
|
|
20
|
+
* promise which resolves when the consumer is done.
|
|
21
|
+
*
|
|
22
|
+
* @param chan
|
|
23
|
+
* @param fn
|
|
24
|
+
* @param num
|
|
25
|
+
*/
|
|
16
26
|
export declare const consumeWith: <T>(chan: Channel<T>, fn: Fn2<T, Channel<T>, void>, num?: number) => Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Similar to {@link consume}, but only processes any current in-flight writes
|
|
29
|
+
* and returns a promise with an array of their values.
|
|
30
|
+
*
|
|
31
|
+
* @param chan
|
|
32
|
+
*/
|
|
17
33
|
export declare const drain: <T>(chan: Channel<T>) => Promise<Awaited<T>[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Takes an async iterable and returns a new CSP {@link Channel}, which receives
|
|
36
|
+
* all values from `src`. If `close` is true (default), the channel will be
|
|
37
|
+
* automatically closed once the iterable is exhausted.
|
|
38
|
+
*
|
|
39
|
+
* @param src
|
|
40
|
+
* @param close
|
|
41
|
+
*/
|
|
18
42
|
export declare const fromAsyncIterable: <T>(src: AsyncIterable<T>, close?: boolean) => Channel<T>;
|
|
19
43
|
export declare const merge: <T>(src: Channel<T>[], dest?: Channel<T>, close?: boolean) => Channel<T>;
|
|
44
|
+
export declare const into: <T, DEST extends IWriteable<T> & IClosable>(chan: DEST, src: Iterable<T> | AsyncIterable<T>, close?: boolean) => Promise<void>;
|
|
20
45
|
export declare const pipe: <T, DEST extends IWriteable<T> & IClosable>(src: Channel<T>, dest: DEST, close?: boolean) => DEST;
|
|
46
|
+
/**
|
|
47
|
+
* Takes one or more input channels and attempts to read from all of them at
|
|
48
|
+
* once (via {@link Channel.race}, a blocking op). Returns a promise which
|
|
49
|
+
* resolves once one of the inputs becomes available or was closed, selects that
|
|
50
|
+
* channel to read from it and returns tuple of `[value, channel]`.
|
|
51
|
+
*
|
|
52
|
+
* @param input
|
|
53
|
+
* @param xs
|
|
54
|
+
*/
|
|
21
55
|
export declare const select: <T>(input: Channel<T>, ...xs: Channel<T>[]) => Promise<[Maybe<T>, Channel<T>]>;
|
|
56
|
+
/**
|
|
57
|
+
* Returns a new {@link Channel} which will automatically close after `delay`
|
|
58
|
+
* milliseconds.
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* Intended as utility for enforcing a timeout for {@link select}-style
|
|
62
|
+
* operations.
|
|
63
|
+
*
|
|
64
|
+
* @param delay
|
|
65
|
+
*/
|
|
22
66
|
export declare const timeout: (delay: number) => Channel<any>;
|
|
23
67
|
//# sourceMappingURL=ops.d.ts.map
|
package/ops.js
CHANGED
|
@@ -73,6 +73,14 @@ const merge = (src, dest, close = true) => {
|
|
|
73
73
|
})();
|
|
74
74
|
return dest;
|
|
75
75
|
};
|
|
76
|
+
const into = async (chan, src, close = false) => {
|
|
77
|
+
for await (let x of src) {
|
|
78
|
+
if (!chan.writable())
|
|
79
|
+
break;
|
|
80
|
+
await chan.write(x);
|
|
81
|
+
}
|
|
82
|
+
close && chan.close();
|
|
83
|
+
};
|
|
76
84
|
const pipe = (src, dest, close = true) => {
|
|
77
85
|
(async () => {
|
|
78
86
|
for await (let x of src) {
|
|
@@ -94,6 +102,7 @@ const select = async (input, ...xs) => {
|
|
|
94
102
|
if (sel.writes.readable()) {
|
|
95
103
|
const [msg, write] = sel.writes.read();
|
|
96
104
|
write(true);
|
|
105
|
+
sel.updateQueue();
|
|
97
106
|
return [msg, sel];
|
|
98
107
|
}
|
|
99
108
|
return [void 0, sel];
|
|
@@ -110,6 +119,7 @@ export {
|
|
|
110
119
|
consumeWith,
|
|
111
120
|
drain,
|
|
112
121
|
fromAsyncIterable,
|
|
122
|
+
into,
|
|
113
123
|
merge,
|
|
114
124
|
pipe,
|
|
115
125
|
select,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/csp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Primitives & operators for Communicating Sequential Processes based on async/await and async iterables",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -106,5 +106,5 @@
|
|
|
106
106
|
"status": "beta",
|
|
107
107
|
"year": 2016
|
|
108
108
|
},
|
|
109
|
-
"gitHead": "
|
|
109
|
+
"gitHead": "85861eb5760fd5c145dbd2d032b6f03400d6653e\n"
|
|
110
110
|
}
|
package/pubsub.d.ts
CHANGED
|
@@ -2,12 +2,31 @@ import type { IObjectOf } from "@thi.ng/api";
|
|
|
2
2
|
import type { IClosable, IWriteable, TopicFn } from "./api.js";
|
|
3
3
|
import { Channel } from "./channel.js";
|
|
4
4
|
import { Mult } from "./mult.js";
|
|
5
|
+
/**
|
|
6
|
+
* Syntax sugar for {@link PubSub} ctor. Creates a new `PubSub` which allows
|
|
7
|
+
* multiple child subscriptions, based on given topic function.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* The topic function will be called for each received value and its result is
|
|
11
|
+
* used to determine which child subscription should receive the value. New
|
|
12
|
+
* topic (un)subscriptions can be created dynamically via
|
|
13
|
+
* {@link PubSub.subscribeTopic} and {@link PubSub.unsubscribeTopic} or
|
|
14
|
+
* {@link PubSub.unsubscribeAll}. Each topic subscription is a {@link Mult},
|
|
15
|
+
* which itself allows for multiple child subscriptions.
|
|
16
|
+
*
|
|
17
|
+
* @param fn
|
|
18
|
+
*/
|
|
5
19
|
export declare function pubsub<T>(fn: TopicFn<T>): PubSub<T>;
|
|
6
20
|
export declare function pubsub<T>(src: Channel<T>, fn: TopicFn<T>): PubSub<T>;
|
|
7
21
|
export declare class PubSub<T> implements IWriteable<T>, IClosable {
|
|
8
22
|
protected src: Channel<T>;
|
|
9
23
|
protected fn: TopicFn<T>;
|
|
10
|
-
protected topics: IObjectOf<Mult<
|
|
24
|
+
protected topics: IObjectOf<Mult<any>>;
|
|
25
|
+
/**
|
|
26
|
+
* See {@link pubsub} for reference.
|
|
27
|
+
*
|
|
28
|
+
* @param fn
|
|
29
|
+
*/
|
|
11
30
|
constructor(fn: TopicFn<T>);
|
|
12
31
|
constructor(src: Channel<T>, fn: TopicFn<T>);
|
|
13
32
|
writable(): boolean;
|
|
@@ -21,8 +40,26 @@ export declare class PubSub<T> implements IWriteable<T>, IClosable {
|
|
|
21
40
|
*
|
|
22
41
|
* @param id - topic id
|
|
23
42
|
*/
|
|
24
|
-
subscribeTopic(id: string): Channel<
|
|
25
|
-
|
|
43
|
+
subscribeTopic<S extends T = T>(id: string): Channel<S>;
|
|
44
|
+
/**
|
|
45
|
+
* Attempts to remove a subscription channel for given topic `id`. Returns
|
|
46
|
+
* true if successful. If `close` is true (default), the given channel will
|
|
47
|
+
* also be closed (only if unsubscription was successful).
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* See {@link Mult.subscribe} for reverse op.
|
|
51
|
+
*
|
|
52
|
+
* @param id
|
|
53
|
+
* @param ch
|
|
54
|
+
* @param close
|
|
55
|
+
*/
|
|
56
|
+
unsubscribeTopic<S extends T = T>(id: string, ch: Channel<S>, close?: boolean): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Removes all child subscription channels for given topic `id` and if
|
|
59
|
+
* `close` is true (default) also closes them.
|
|
60
|
+
*
|
|
61
|
+
* @param close
|
|
62
|
+
*/
|
|
26
63
|
unsubscribeAll(id: string, close?: boolean): void;
|
|
27
64
|
protected process(): Promise<void>;
|
|
28
65
|
}
|
package/pubsub.js
CHANGED
|
@@ -50,10 +50,28 @@ class PubSub {
|
|
|
50
50
|
}
|
|
51
51
|
return topic.subscribe();
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Attempts to remove a subscription channel for given topic `id`. Returns
|
|
55
|
+
* true if successful. If `close` is true (default), the given channel will
|
|
56
|
+
* also be closed (only if unsubscription was successful).
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* See {@link Mult.subscribe} for reverse op.
|
|
60
|
+
*
|
|
61
|
+
* @param id
|
|
62
|
+
* @param ch
|
|
63
|
+
* @param close
|
|
64
|
+
*/
|
|
65
|
+
unsubscribeTopic(id, ch, close = true) {
|
|
54
66
|
const topic = this.topics[id];
|
|
55
|
-
return topic?.unsubscribe(ch) ?? false;
|
|
67
|
+
return topic?.unsubscribe(ch, close) ?? false;
|
|
56
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Removes all child subscription channels for given topic `id` and if
|
|
71
|
+
* `close` is true (default) also closes them.
|
|
72
|
+
*
|
|
73
|
+
* @param close
|
|
74
|
+
*/
|
|
57
75
|
unsubscribeAll(id, close = true) {
|
|
58
76
|
const topic = this.topics[id];
|
|
59
77
|
topic?.unsubscribeAll(close);
|