@thi.ng/csp 2.1.115 → 3.0.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/channel.js CHANGED
@@ -1,519 +1,170 @@
1
- import { shuffle } from "@thi.ng/arrays/shuffle";
2
- import { isFunction } from "@thi.ng/checks/is-function";
3
- import { DCons } from "@thi.ng/dcons/dcons";
4
- import { illegalArity } from "@thi.ng/errors/illegal-arity";
5
- import { cycle } from "@thi.ng/transducers/cycle";
6
- import { delayed } from "@thi.ng/transducers/delayed";
7
- import { range } from "@thi.ng/transducers/range";
8
- import { isReduced, unreduced } from "@thi.ng/transducers/reduced";
9
- import { FixedBuffer } from "./buffer.js";
10
- var State = /* @__PURE__ */ ((State2) => {
11
- State2[State2["OPEN"] = 0] = "OPEN";
12
- State2[State2["CLOSED"] = 1] = "CLOSED";
13
- State2[State2["DONE"] = 2] = "DONE";
14
- return State2;
15
- })(State || {});
1
+ import { fifo } from "@thi.ng/buffers/fifo";
2
+ import { isNumber } from "@thi.ng/checks/is-number";
3
+ import { isPlainObject } from "@thi.ng/checks/is-plain-object";
4
+ import { illegalState } from "@thi.ng/errors/illegal-state";
5
+ import { __nextID } from "./idgen.js";
6
+ const MAX_READS = 1024;
7
+ const MAX_QUEUE = 1024;
8
+ const STATE_OPEN = 0;
9
+ const STATE_CLOSING = 1;
10
+ const STATE_CLOSED = 2;
11
+ function channel(...args) {
12
+ return new Channel(...args);
13
+ }
16
14
  class Channel {
17
- static constantly(x, delay) {
18
- const chan = new Channel(delay ? delayed(delay) : null);
19
- chan.produce(() => x);
20
- return chan;
21
- }
22
- static repeatedly(fn, delay) {
23
- const chan = new Channel(delay ? delayed(delay) : null);
24
- chan.produce(fn);
25
- return chan;
26
- }
27
- static cycle(src, delay) {
28
- return Channel.from(cycle(src), delay ? delayed(delay) : null);
29
- }
30
- static range(...args) {
31
- const [from, to, step, delay] = args;
32
- return Channel.from(
33
- range(from, to, step),
34
- delay !== void 0 ? delayed(delay) : null
35
- );
36
- }
37
- /**
38
- * Constructs new channel which closes automatically after given period.
39
- *
40
- * @param delay - time in ms
41
- */
42
- static timeout(delay) {
43
- const chan = new Channel(`timeout-${Channel.NEXT_ID++}`);
44
- setTimeout(() => chan.close(), delay);
45
- return chan;
46
- }
47
- /**
48
- * Shorthand for: `Channel.timeout(delay).take()`
49
- *
50
- * @param delay - time in ms
51
- */
52
- static sleep(delay) {
53
- return Channel.timeout(delay).read();
54
- }
55
- /**
56
- * Creates new channel with single value from given promise, then closes
57
- * automatically iff promise has been resolved.
58
- *
59
- * @param p - promise
60
- */
61
- static fromPromise(p) {
62
- const chan = new Channel();
63
- p.then(
64
- (x) => (async () => {
65
- await chan.write(x);
66
- await chan.close();
67
- return x;
68
- })()
69
- );
70
- return chan;
71
- }
72
- static from(...args) {
73
- let close, tx;
15
+ id;
16
+ writes;
17
+ queue = [];
18
+ reads = [];
19
+ races = [];
20
+ state = STATE_OPEN;
21
+ constructor(...args) {
22
+ let buf = 1;
23
+ let opts;
74
24
  switch (args.length) {
75
25
  case 1:
26
+ if (isPlainObject(args[0]))
27
+ opts = args[0];
28
+ else
29
+ buf = args[0];
76
30
  break;
77
31
  case 2:
78
- if (typeof args[1] === "boolean") {
79
- close = args[1];
80
- } else {
81
- tx = args[1];
82
- }
32
+ [buf, opts] = args;
83
33
  break;
84
- case 3:
85
- tx = args[1];
86
- close = args[2];
87
- break;
88
- default:
89
- illegalArity(args.length);
90
34
  }
91
- const chan = new Channel(tx);
92
- chan.into(args[0], close);
93
- return chan;
35
+ this.writes = isNumber(buf) ? fifo(buf) : buf;
36
+ this.id = opts?.id ?? `chan-${__nextID()}`;
37
+ }
38
+ async *[Symbol.asyncIterator]() {
39
+ while (this.state < STATE_CLOSED) {
40
+ const x = await this.read();
41
+ if (x !== void 0)
42
+ yield x;
43
+ }
94
44
  }
95
- /**
96
- * Takes an array of channels and blocks until any of them becomes
97
- * readable (or has been closed). The returned promised resolves into
98
- * an array of `[value, channel]`. Channel order is repeatedly
99
- * shuffled for each read attempt.
100
- *
101
- * @param chans - source channels
102
- */
103
- static select(chans) {
45
+ read() {
104
46
  return new Promise((resolve) => {
105
- const _select = () => {
106
- for (let c of shuffle(chans)) {
107
- if (c.isReadable() || c.isClosed()) {
108
- c.read().then((x) => resolve([x, c]));
109
- return;
110
- }
111
- }
112
- Channel.SCHEDULE.call(null, _select, 0);
113
- };
114
- Channel.SCHEDULE.call(null, _select, 0);
115
- });
116
- }
117
- /**
118
- * Takes an array of channels to merge into new channel. Any closed
119
- * channels will be automatically removed from the input selection.
120
- * Once all inputs are closed, the target channel will close too (by
121
- * default).
122
- *
123
- * @remarks
124
- * If `named` is true, the merged channel will have tuples of:
125
- * `[src-id, val]` If false (default), only received values will be
126
- * forwarded.
127
- *
128
- * @param chans - source channels
129
- * @param out - result channel
130
- * @param close - true, if result closes
131
- * @param named - true, to emit labeled tuples
132
- */
133
- static merge(chans, out, close = true, named = false) {
134
- out = out || new Channel();
135
- (async () => {
136
- while (true) {
137
- let [x, ch] = await Channel.select(chans);
138
- if (x === void 0) {
139
- chans.splice(chans.indexOf(ch), 1);
140
- if (!chans.length) {
141
- close && await out.close();
142
- break;
143
- }
144
- } else {
145
- await out.write(named ? [ch.id, x] : x);
146
- }
47
+ if (!this.readable()) {
48
+ resolve(void 0);
49
+ return;
147
50
  }
148
- })();
149
- return out;
150
- }
151
- /**
152
- * Takes an array of channels to merge into new channel of tuples.
153
- * Whereas `Channel.merge()` realizes a sequential merging with no
154
- * guarantees about ordering of the output.
155
- *
156
- * @remarks
157
- * The output channel of this function will collect values from all
158
- * channels and a new tuple is emitted only once a new value has
159
- * been read from ALL channels. Therefore the overall throughput is
160
- * dictated by the slowest of the inputs.
161
- *
162
- * Once any of the inputs closes, the process is terminated and the
163
- * output channel is closed too (by default).
164
- *
165
- * @example
166
- * ```ts tangle:../export/merge-tuples.ts
167
- * import { Channel } from "@thi.ng/csp";
168
- *
169
- * Channel.mergeTuples([
170
- * Channel.from([1, 2, 3]),
171
- * Channel.from([10, 20, 30]),
172
- * Channel.from([100, 200, 300])
173
- * ]).consume();
174
- *
175
- * // chan-0 : [ 1, 10, 100 ]
176
- * // chan-0 : [ 2, 20, 200 ]
177
- * // chan-0 : [ 3, 30, 300 ]
178
- * // chan-0 done
179
- *
180
- * Channel.mergeTuples([
181
- * Channel.from([1, 2, 3]),
182
- * Channel.from([10, 20, 30]),
183
- * Channel.from([100, 200, 300])
184
- * ], null, false).consume();
185
- * ```
186
- *
187
- * @param chans - source channels
188
- * @param out - result channel
189
- * @param closeOnFirst - true, if result closes when first input is done
190
- * @param closeOutput - true, if result closes when all inputs are done
191
- */
192
- static mergeTuples(chans, out, closeOnFirst = true, closeOutput = true) {
193
- out = out || new Channel();
194
- (async () => {
195
- let buf = [];
196
- let orig = [...chans];
197
- let sel = new Set(chans);
198
- let n = chans.length;
199
- while (true) {
200
- let [x, ch] = await Channel.select([...sel]);
201
- let idx = orig.indexOf(ch);
202
- if (x === void 0) {
203
- if (closeOnFirst || chans.length === 1) {
204
- break;
205
- }
206
- chans.splice(idx, 1);
207
- }
208
- buf[idx] = x;
209
- sel.delete(ch);
210
- if (--n === 0) {
211
- await out.write(buf);
212
- buf = [];
213
- n = chans.length;
214
- sel = new Set(chans);
51
+ if (this.state < STATE_CLOSING || this.writes.readable()) {
52
+ if (this.reads.length < MAX_READS) {
53
+ this.reads.push(resolve);
54
+ } else {
55
+ resolve(void 0);
215
56
  }
216
57
  }
217
- closeOutput && await out.close();
218
- })();
219
- return out;
58
+ if (this.writes.readable())
59
+ this.deliver();
60
+ });
220
61
  }
221
- static MAX_WRITES = 1024;
222
- static NEXT_ID = 0;
223
- static SCHEDULE = typeof setImmediate === "function" ? setImmediate : setTimeout;
224
- static RFN = [
225
- () => null,
226
- (acc) => acc,
227
- (acc, x) => acc.push(x)
228
- ];
229
- id;
230
- onerror;
231
- state;
232
- buf;
233
- tx;
234
- writes;
235
- reads;
236
- txbuf;
237
- isBusy;
238
- constructor(...args) {
239
- let id, buf, tx, err;
240
- let [a, b] = args;
241
- switch (args.length) {
242
- case 0:
243
- break;
244
- case 1:
245
- if (typeof a === "string") {
246
- id = a;
247
- } else if (maybeBuffer(a)) {
248
- buf = a;
249
- } else {
250
- tx = a;
251
- }
252
- break;
253
- case 2:
254
- if (typeof a === "string") {
255
- id = a;
256
- if (maybeBuffer(b)) {
257
- buf = b;
258
- } else {
259
- tx = b;
260
- }
261
- } else {
262
- [tx, err] = args;
263
- }
264
- break;
265
- case 3:
266
- if (isFunction(args[1]) && isFunction(args[2])) {
267
- [id, tx, err] = args;
268
- } else {
269
- [id, buf, tx] = args;
270
- }
271
- break;
272
- case 4:
273
- [id, buf, tx, err] = args;
274
- break;
275
- default:
276
- illegalArity(args.length);
62
+ poll() {
63
+ const { reads, writes } = this;
64
+ if (this.readable() && !reads.length && writes.readable()) {
65
+ const [msg, write] = writes.read();
66
+ write(true);
67
+ this.updateQueue();
68
+ return msg;
277
69
  }
278
- this.id = id || `chan-${Channel.NEXT_ID++}`;
279
- buf = buf || 1;
280
- this.buf = typeof buf === "number" ? new FixedBuffer(buf) : buf;
281
- this.writes = new DCons();
282
- this.reads = new DCons();
283
- this.txbuf = new DCons();
284
- this.tx = tx ? tx(Channel.RFN) : null;
285
- this.onerror = tx && (err || defaultErrorHandler);
286
- this.state = 0 /* OPEN */;
287
- this.isBusy = false;
288
70
  }
289
- channel() {
290
- return this;
291
- }
292
- write(value) {
71
+ write(msg) {
293
72
  return new Promise((resolve) => {
294
- if (this.state !== 0 /* OPEN */) {
73
+ if (!this.writable()) {
295
74
  resolve(false);
75
+ return;
296
76
  }
297
- if (this.writes.length < Channel.MAX_WRITES) {
298
- this.writes.push({
299
- value: this.tx ? async () => {
300
- try {
301
- if (isReduced(this.tx[2](this.txbuf, value))) {
302
- this.state = 1 /* CLOSED */;
303
- }
304
- } catch (e) {
305
- this.onerror(e, this, value);
306
- }
307
- } : () => value,
308
- resolve
309
- });
310
- this.process();
311
- } else {
312
- throw new Error(
313
- `channel stalled (${Channel.MAX_WRITES} unprocessed writes)`
77
+ const { reads, writes, races, queue } = this;
78
+ const val = [msg, resolve];
79
+ if (!(writes.writable() && writes.write(val))) {
80
+ queue.length < MAX_QUEUE ? queue.push(val) : illegalState(
81
+ "max. queue capacity reached, reduce back pressure!"
314
82
  );
315
83
  }
316
- });
317
- }
318
- read() {
319
- return new Promise((resolve) => {
320
- if (this.state === 2 /* DONE */) {
321
- resolve(void 0);
84
+ if (reads.length) {
85
+ this.deliver();
86
+ } else if (races.length) {
87
+ races.shift()(this);
322
88
  }
323
- this.reads.push(resolve);
324
- this.process();
325
89
  });
326
90
  }
327
- tryRead(timeout = 1e3) {
328
- return new Promise((resolve) => {
329
- (async () => resolve(
330
- (await Channel.select([this, Channel.timeout(timeout)]))[0]
331
- ))();
332
- });
333
- }
334
- close(flush = false) {
335
- if (this.state === 0 /* OPEN */) {
336
- this.state = 1 /* CLOSED */;
337
- flush && this.flush();
338
- return this.process();
91
+ offer(msg) {
92
+ if (this.writable() && this.writes.writable()) {
93
+ this.write(msg);
94
+ return true;
339
95
  }
96
+ return false;
340
97
  }
341
- isClosed() {
342
- return this.state !== 0 /* OPEN */;
343
- }
344
- isReadable() {
345
- return this.state !== 2 /* DONE */ && this.buf && this.buf.length > 0 || this.writes && this.writes.length > 0 || this.txbuf && this.txbuf.length > 0;
346
- }
347
- consume(fn = (x) => console.log(this.id, ":", x)) {
348
- return (async () => {
349
- let x;
350
- while ((x = null, x = await this.read()) !== void 0) {
351
- await fn(x);
352
- }
353
- })();
354
- }
355
- produce(fn, close = true) {
356
- return (async () => {
357
- while (!this.isClosed()) {
358
- const val = await fn();
359
- if (val === void 0) {
360
- close && await this.close();
361
- break;
362
- }
363
- await this.write(val);
364
- }
365
- })();
366
- }
367
- consumeWhileReadable(fn = (x) => console.log(this.id, ":", x)) {
368
- return (async () => {
369
- let x;
370
- while (this.isReadable()) {
371
- x = await this.read();
372
- if (x === void 0) {
373
- break;
374
- }
375
- await fn(x);
376
- x = null;
377
- }
378
- })();
379
- }
380
- reduce(rfn, acc) {
381
- return (async () => {
382
- const [init, complete, reduce] = rfn;
383
- acc = acc != null ? acc : init();
384
- let x;
385
- while ((x = null, x = await this.read()) !== void 0) {
386
- acc = reduce(acc, x);
387
- if (isReduced(acc)) {
388
- acc = acc.deref();
389
- break;
390
- }
98
+ race() {
99
+ return new Promise((resolve) => {
100
+ if (!this.readable()) {
101
+ resolve(this);
102
+ return;
391
103
  }
392
- return unreduced(complete(acc));
393
- })();
394
- }
395
- transduce(tx, rfn, acc) {
396
- return (async () => {
397
- const _rfn = tx(rfn);
398
- return unreduced(_rfn[1](await this.reduce(_rfn, acc)));
399
- })();
400
- }
401
- into(src, close = true) {
402
- return (async () => {
403
- for (let x of src) {
404
- if (this.isClosed()) {
405
- break;
406
- }
407
- await this.write(x);
104
+ this.races.push(resolve);
105
+ if (this.writes.readable()) {
106
+ this.races.shift()(this);
408
107
  }
409
- close && await this.close();
410
- })();
411
- }
412
- pipe(dest, close = true) {
413
- if (!(dest instanceof Channel)) {
414
- dest = new Channel(dest);
415
- }
416
- this.consume((x) => dest.write(x)).then(() => {
417
- close && dest.close();
418
108
  });
419
- return dest;
420
109
  }
421
- split(pred, truthy, falsey, close = true) {
422
- if (!(truthy instanceof Channel)) {
423
- truthy = new Channel();
424
- }
425
- if (!(falsey instanceof Channel)) {
426
- falsey = new Channel();
110
+ close() {
111
+ if (this.state >= STATE_CLOSING)
112
+ return;
113
+ const { reads, writes, races } = this;
114
+ this.state = reads.length || writes.readable() ? STATE_CLOSING : STATE_CLOSED;
115
+ while (reads.length && writes.readable())
116
+ this.deliver();
117
+ if (!writes.readable()) {
118
+ while (reads.length)
119
+ reads.shift()(void 0);
427
120
  }
428
- this.consume((x) => (pred(x) ? truthy : falsey).write(x)).then(
429
- () => {
430
- close && (truthy.close(), falsey.close());
431
- }
432
- );
433
- return [truthy, falsey];
434
- }
435
- concat(chans, close = true) {
436
- return (async () => {
437
- for (let c of chans) {
438
- await c.consume((x) => this.write(x));
439
- }
440
- close && await this.close();
441
- })();
121
+ this.state = writes.readable() ? STATE_CLOSING : STATE_CLOSED;
122
+ while (races.length)
123
+ races.shift()(this);
442
124
  }
443
- release() {
444
- if (this.state === 1 /* CLOSED */) {
445
- this.state = 2 /* DONE */;
446
- this.flush();
447
- this.buf.release();
448
- delete this.reads;
449
- delete this.writes;
450
- delete this.buf;
451
- delete this.txbuf;
452
- delete this.tx;
453
- delete this.isBusy;
454
- delete this.onerror;
455
- }
125
+ /**
126
+ * Returns true if the channel is principally readable (i.e. not yet
127
+ * closed), however there might not be any values available yet and reads
128
+ * might block.
129
+ */
130
+ readable() {
131
+ return this.state < STATE_CLOSED;
456
132
  }
457
- async process() {
458
- if (!this.isBusy) {
459
- this.isBusy = true;
460
- const { buf, txbuf, reads, writes } = this;
461
- let doProcess = true;
462
- while (doProcess) {
463
- while (reads.length && (txbuf.length || buf.length)) {
464
- if (txbuf.length) {
465
- const val = txbuf.drop();
466
- if (val !== void 0) {
467
- reads.drop()(val);
468
- }
469
- } else {
470
- const val = await buf.drop().value();
471
- if (val !== void 0) {
472
- reads.drop()(val);
473
- }
474
- }
475
- }
476
- while (writes.length && !buf.isFull()) {
477
- const put = writes.drop();
478
- buf.push(put);
479
- put.resolve(true);
480
- }
481
- if (this.state === 1 /* CLOSED */) {
482
- if (this.tx && !writes.length) {
483
- try {
484
- this.tx[1](this.txbuf);
485
- } catch (e) {
486
- this.onerror(e, this);
487
- }
488
- }
489
- if (!this.isReadable()) {
490
- this.release();
491
- return;
492
- }
493
- }
494
- doProcess = reads.length && (txbuf.length || buf.length) || writes.length && !buf.isFull();
495
- }
496
- this.isBusy = false;
497
- }
133
+ /**
134
+ * Returns true if the channel is principally writable (i.e. not closing or
135
+ * closed), however depending on buffer behavior the channel might not yet
136
+ * accept new values and writes might block.
137
+ */
138
+ writable() {
139
+ return this.state === STATE_OPEN;
498
140
  }
499
- flush() {
500
- let op;
501
- while (op = this.reads.drop()) {
502
- op();
141
+ /**
142
+ * Returns true if the channel is fully closed and no further reads or
143
+ * writes are possible.
144
+ */
145
+ closed() {
146
+ return this.state === STATE_CLOSED;
147
+ }
148
+ deliver() {
149
+ const { reads, writes } = this;
150
+ const [msg, write] = writes.read();
151
+ write(true);
152
+ reads.shift()(msg);
153
+ this.updateQueue();
154
+ }
155
+ updateQueue() {
156
+ const { queue, writes } = this;
157
+ if (queue.length && writes.writable()) {
158
+ writes.write(queue.shift());
503
159
  }
504
- while (op = this.writes.drop()) {
505
- op.resolve(false);
160
+ if (this.state === STATE_CLOSING && !writes.readable()) {
161
+ this.state = STATE_CLOSED;
506
162
  }
507
- this.buf.release();
508
163
  }
509
164
  }
510
- const defaultErrorHandler = (e, chan, val) => console.log(
511
- chan.id,
512
- "error occurred",
513
- e.message,
514
- val !== void 0 ? val : ""
515
- );
516
- const maybeBuffer = (x) => x instanceof FixedBuffer || typeof x === "number";
517
165
  export {
518
- Channel
166
+ Channel,
167
+ MAX_QUEUE,
168
+ MAX_READS,
169
+ channel
519
170
  };
package/idgen.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /** @internal */
2
+ export declare const __nextID: () => number;
3
+ //# sourceMappingURL=idgen.d.ts.map
package/idgen.js ADDED
@@ -0,0 +1,5 @@
1
+ let NEXT_ID = 0;
2
+ const __nextID = () => NEXT_ID++;
3
+ export {
4
+ __nextID
5
+ };
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./api.js";
2
- export * from "./buffer.js";
3
2
  export * from "./channel.js";
4
3
  export * from "./mult.js";
4
+ export * from "./ops.js";
5
5
  export * from "./pubsub.js";
6
6
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./api.js";
2
- export * from "./buffer.js";
3
2
  export * from "./channel.js";
4
3
  export * from "./mult.js";
4
+ export * from "./ops.js";
5
5
  export * from "./pubsub.js";