@thi.ng/transducers-async 0.1.2 → 0.2.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2024-04-13T16:05:36Z
3
+ - **Last updated**: 2024-04-22T09:56:21Z
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
+ ## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/transducers-async@0.2.0) (2024-04-20)
13
+
14
+ #### 🚀 Features
15
+
16
+ - add delayed() & wait() helpers ([d2f0738](https://github.com/thi-ng/umbrella/commit/d2f0738))
17
+ - add mult() for 1:N splitting (multiple subscribers) ([ad1b63b](https://github.com/thi-ng/umbrella/commit/ad1b63b))
18
+ - add asAsyncIterable() helper ([8e40424](https://github.com/thi-ng/umbrella/commit/8e40424))
19
+ - add source(), refactor events() ([b94d150](https://github.com/thi-ng/umbrella/commit/b94d150))
20
+ - add syncRAF(), update sidechain() args ([54ded8b](https://github.com/thi-ng/umbrella/commit/54ded8b))
21
+ - add pubsub() ([1f406d0](https://github.com/thi-ng/umbrella/commit/1f406d0))
22
+ - update/extend Source interface/impl ([a95819b](https://github.com/thi-ng/umbrella/commit/a95819b))
23
+ - add [@thi.ng/buffers](https://github.com/thi-ng/umbrella/tree/main/packages/buffers) support for source() ([c46d862](https://github.com/thi-ng/umbrella/commit/c46d862))
24
+ - update Source API, add docs ([7a7b5ab](https://github.com/thi-ng/umbrella/commit/7a7b5ab))
25
+
26
+ #### ♻️ Refactoring
27
+
28
+ - add ClosableAsyncGenerator type, update events() & source() ([929d860](https://github.com/thi-ng/umbrella/commit/929d860))
29
+ - simplify raf() impl ([38c36b9](https://github.com/thi-ng/umbrella/commit/38c36b9))
30
+ - rewrite to use source()
31
+ - update type usage ([bec044c](https://github.com/thi-ng/umbrella/commit/bec044c))
32
+
12
33
  ### [0.1.1](https://github.com/thi-ng/umbrella/tree/@thi.ng/transducers-async@0.1.1) (2024-04-11)
13
34
 
14
35
  #### ♻️ Refactoring
package/README.md CHANGED
@@ -16,12 +16,14 @@
16
16
 
17
17
  - [About](#about)
18
18
  - [Evaluators](#evaluators)
19
- - [Iterators](#iterators)
19
+ - [Generators & iterators](#generators--iterators)
20
+ - [Combinators](#combinators)
20
21
  - [Transducers](#transducers)
21
22
  - [Reducers](#reducers)
22
23
  - [Status](#status)
23
24
  - [Installation](#installation)
24
25
  - [Dependencies](#dependencies)
26
+ - [Usage examples](#usage-examples)
25
27
  - [API](#api)
26
28
  - [Authors](#authors)
27
29
  - [License](#license)
@@ -38,18 +40,26 @@ Async versions of various highly composable transducers, reducers and iterators.
38
40
  - [`step()`](https://docs.thi.ng/umbrella/transducers-async/functions/step.html)
39
41
  - [`transduce()`](https://docs.thi.ng/umbrella/transducers-async/functions/transduce.html)
40
42
 
41
- ### Iterators
43
+ ### Generators & iterators
42
44
 
45
+ - [`asAsyncIterable()`](https://docs.thi.ng/umbrella/transducers-async/functions/asAsyncIterable.html)
43
46
  - [`concat()`](https://docs.thi.ng/umbrella/transducers-async/functions/concat.html)
44
47
  - [`events()`](https://docs.thi.ng/umbrella/transducers-async/functions/events.html)
45
- - [`merge()`](https://docs.thi.ng/umbrella/transducers-async/functions/merge.html)
46
48
  - [`raf()`](https://docs.thi.ng/umbrella/transducers-async/functions/raf.html)
47
49
  - [`range()`](https://docs.thi.ng/umbrella/transducers-async/functions/range.html)
48
50
  - [`repeatedly()`](https://docs.thi.ng/umbrella/transducers-async/functions/repeatedly.html)
49
51
  - [`sidechain()`](https://docs.thi.ng/umbrella/transducers-async/functions/sidechain.html)
50
- - [`sync()`](https://docs.thi.ng/umbrella/transducers-async/functions/sync.html)
52
+ - [`source()`](https://docs.thi.ng/umbrella/transducers-async/functions/source.html)
51
53
  - [`zip()`](https://docs.thi.ng/umbrella/transducers-async/functions/zip.html)
52
54
 
55
+ ### Combinators
56
+
57
+ - [`merge()`](https://docs.thi.ng/umbrella/transducers-async/functions/merge.html)
58
+ - [`mult()`](https://docs.thi.ng/umbrella/transducers-async/functions/mult.html)
59
+ - [`pubsub()`](https://docs.thi.ng/umbrella/transducers-async/functions/pubsub.html)
60
+ - [`sync()`](https://docs.thi.ng/umbrella/transducers-async/functions/sync.html)
61
+ - [`syncRAF()`](https://docs.thi.ng/umbrella/transducers-async/functions/syncRAF.html)
62
+
53
63
  ### Transducers
54
64
 
55
65
  - [`comp()`](https://docs.thi.ng/umbrella/transducers-async/functions/comp.html)
@@ -58,6 +68,7 @@ Async versions of various highly composable transducers, reducers and iterators.
58
68
  - [`mapcat()`](https://docs.thi.ng/umbrella/transducers-async/functions/mapcat.html)
59
69
  - [`multiplex()`](https://docs.thi.ng/umbrella/transducers-async/functions/multiplex.html)
60
70
  - [`multiplexObj()`](https://docs.thi.ng/umbrella/transducers-async/functions/multiplexObj.html)
71
+ - [`partition()`](https://docs.thi.ng/umbrella/transducers-async/functions/partition.html)
61
72
  - [`take()`](https://docs.thi.ng/umbrella/transducers-async/functions/take.html)
62
73
  - [`throttle()`](https://docs.thi.ng/umbrella/transducers-async/functions/throttle.html)
63
74
  - [`throttleTime()`](https://docs.thi.ng/umbrella/transducers-async/functions/throttleTime.html)
@@ -87,10 +98,10 @@ import * as txa from "@thi.ng/transducers-async";
87
98
  Browser ESM import:
88
99
 
89
100
  ```html
90
- <script type="module" src="https://cdn.skypack.dev/@thi.ng/transducers-async"></script>
101
+ <script type="module" src="https://esm.run/@thi.ng/transducers-async"></script>
91
102
  ```
92
103
 
93
- [Skypack documentation](https://docs.skypack.dev/)
104
+ [JSDelivr documentation](https://www.jsdelivr.com/)
94
105
 
95
106
  For Node.js REPL:
96
107
 
@@ -98,20 +109,33 @@ For Node.js REPL:
98
109
  const txa = await import("@thi.ng/transducers-async");
99
110
  ```
100
111
 
101
- Package sizes (brotli'd, pre-treeshake): ESM: 2.23 KB
112
+ Package sizes (brotli'd, pre-treeshake): ESM: 3.10 KB
102
113
 
103
114
  ## Dependencies
104
115
 
105
116
  - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
117
+ - [@thi.ng/buffers](https://github.com/thi-ng/umbrella/tree/develop/packages/buffers)
106
118
  - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
107
119
  - [@thi.ng/compose](https://github.com/thi-ng/umbrella/tree/develop/packages/compose)
108
120
  - [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
109
121
  - [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/develop/packages/transducers)
110
122
 
123
+ ## Usage examples
124
+
125
+ One project in this repo's
126
+ [/examples](https://github.com/thi-ng/umbrella/tree/develop/examples)
127
+ directory is using this package:
128
+
129
+ | Description | Live demo | Source |
130
+ |:----------------------------------------------------------|:-------------------------------------------------|:------------------------------------------------------------------------------|
131
+ | Basic & barebones usage of async iterables in thi.ng/rdom | [Demo](https://demo.thi.ng/umbrella/rdom-async/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/rdom-async) |
132
+
111
133
  ## API
112
134
 
113
135
  [Generated API docs](https://docs.thi.ng/umbrella/transducers-async/)
114
136
 
137
+ TODO
138
+
115
139
  ## Authors
116
140
 
117
141
  - [Karsten Schmidt](https://thi.ng)
package/api.d.ts CHANGED
@@ -20,4 +20,10 @@ export type AsyncMultiplexTxLike<A, B> = AsyncTxLike<A, B> | [AsyncTxLike<A, B>,
20
20
  export interface IXformAsync<A, B> {
21
21
  xformAsync(): AsyncTransducer<A, B>;
22
22
  }
23
+ export interface ClosableAsyncGenerator<T> extends AsyncGenerator<T> {
24
+ /**
25
+ * Terminates the iterable at the next opportunity.
26
+ */
27
+ close(): void;
28
+ }
23
29
  //# sourceMappingURL=api.d.ts.map
package/as-async.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function asAsyncIterable<T>(src: Iterable<T>, delay?: number): AsyncGenerator<Awaited<T>, void, unknown>;
2
+ //# sourceMappingURL=as-async.d.ts.map
package/as-async.js ADDED
@@ -0,0 +1,11 @@
1
+ import { wait } from "./delayed.js";
2
+ async function* asAsyncIterable(src, delay = 0) {
3
+ for (let x of src) {
4
+ yield x;
5
+ if (delay > 0)
6
+ await wait(delay);
7
+ }
8
+ }
9
+ export {
10
+ asAsyncIterable
11
+ };
package/delayed.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Same as [thi.ng/compose `delayed()`](). Re-exported for convenience. Yields
3
+ * `x` as promise which only resolves after `delay` milliseconds.
4
+ *
5
+ * @remarks
6
+ * Also see {@link wait}.
7
+ *
8
+ * @param x
9
+ * @param delay
10
+ */
11
+ export declare const delayed: <T>(x: T, t: number) => Promise<T>;
12
+ /**
13
+ * Syntax sugar for {@link delayed}, yields a void promise which resolves after
14
+ * `delay` milliseconds.
15
+ *
16
+ * @remarks
17
+ * Also see {@link delayed}.
18
+ *
19
+ * @param delay
20
+ */
21
+ export declare const wait: (delay: number) => Promise<void>;
22
+ //# sourceMappingURL=delayed.d.ts.map
package/delayed.js ADDED
@@ -0,0 +1,7 @@
1
+ import { delayed as $ } from "@thi.ng/compose/delayed";
2
+ const delayed = $;
3
+ const wait = (delay) => delayed(void 0, delay);
4
+ export {
5
+ delayed,
6
+ wait
7
+ };
package/events.d.ts CHANGED
@@ -1,14 +1,35 @@
1
+ import type { IDeref, Maybe } from "@thi.ng/api";
2
+ import type { ClosableAsyncGenerator } from "./api.js";
1
3
  /**
2
4
  * Attaches an event listener to given `target` and yields an async iterator of
3
5
  * events.
4
6
  *
5
7
  * @remarks
6
- * The event listener can be removed (and the iterator stopped) by sending
7
- * `yield iter.next(true)`.
8
+ * The event listener can be removed (and the iterator stopped) by calling
9
+ * `.close()`.
10
+ *
11
+ * @example
12
+ * ```ts tangle:../export/events.ts
13
+ * import { events, map, run } from "@thi.ng/transducers-async";
14
+ *
15
+ * const resize = events(window, "resize");
16
+ *
17
+ * const sizes = map(
18
+ * (e) => [window.innerWidth, window.innerHeight],
19
+ * resize
20
+ * );
21
+ *
22
+ * for await (let [w, h] of sizes) {
23
+ * console.log(w, h)
24
+ * }
25
+ *
26
+ * // to stop listening and stop iterator
27
+ * resize.close();
28
+ * ```
8
29
  *
9
30
  * @param target
10
31
  * @param id
11
32
  * @param opts
12
33
  */
13
- export declare function events<T extends Event = Event>(target: EventTarget, id: string, opts?: EventListenerOptions): AsyncGenerator<T>;
34
+ export declare const events: <T extends Event = Event>(target: EventTarget, id: string, opts?: EventListenerOptions) => ClosableAsyncGenerator<T> & IDeref<Maybe<T>>;
14
35
  //# sourceMappingURL=events.d.ts.map
package/events.js CHANGED
@@ -1,17 +1,14 @@
1
- async function* events(target, id, opts) {
2
- let resolve;
3
- const listener = (e) => resolve(e);
1
+ import { source } from "./source.js";
2
+ const events = (target, id, opts) => {
3
+ const listener = (e) => gen.write(e);
4
4
  target.addEventListener(id, listener, opts);
5
- while (true) {
6
- const promise = new Promise(($resolve) => {
7
- resolve = $resolve;
8
- });
9
- const cancel = yield await promise;
10
- if (cancel === true)
11
- break;
12
- }
13
- target.removeEventListener(id, listener, opts);
14
- }
5
+ const gen = source();
6
+ gen.close = () => {
7
+ target.removeEventListener(id, listener, opts);
8
+ gen.write(void 0);
9
+ };
10
+ return gen;
11
+ };
15
12
  export {
16
13
  events
17
14
  };
package/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export * from "./api.js";
2
+ export * from "./as-async.js";
2
3
  export * from "./cat.js";
3
4
  export * from "./comp.js";
4
5
  export * from "./compr.js";
5
6
  export * from "./concat.js";
7
+ export * from "./delayed.js";
6
8
  export * from "./ensure.js";
7
9
  export * from "./events.js";
8
10
  export * from "./filter.js";
@@ -10,9 +12,11 @@ export * from "./iterator.js";
10
12
  export * from "./map.js";
11
13
  export * from "./mapcat.js";
12
14
  export * from "./merge.js";
15
+ export * from "./mult.js";
13
16
  export * from "./multiplex-obj.js";
14
17
  export * from "./multiplex.js";
15
18
  export * from "./partition.js";
19
+ export * from "./pubsub.js";
16
20
  export * from "./push.js";
17
21
  export * from "./raf.js";
18
22
  export * from "./range.js";
@@ -20,8 +24,10 @@ export * from "./reduce.js";
20
24
  export * from "./repeatedly.js";
21
25
  export * from "./run.js";
22
26
  export * from "./sidechain.js";
27
+ export * from "./source.js";
23
28
  export * from "./step.js";
24
29
  export * from "./sync.js";
30
+ export * from "./sync-raf.js";
25
31
  export * from "./take.js";
26
32
  export * from "./throttle-time.js";
27
33
  export * from "./throttle.js";
package/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  export * from "./api.js";
2
+ export * from "./as-async.js";
2
3
  export * from "./cat.js";
3
4
  export * from "./comp.js";
4
5
  export * from "./compr.js";
5
6
  export * from "./concat.js";
7
+ export * from "./delayed.js";
6
8
  export * from "./ensure.js";
7
9
  export * from "./events.js";
8
10
  export * from "./filter.js";
@@ -10,9 +12,11 @@ export * from "./iterator.js";
10
12
  export * from "./map.js";
11
13
  export * from "./mapcat.js";
12
14
  export * from "./merge.js";
15
+ export * from "./mult.js";
13
16
  export * from "./multiplex-obj.js";
14
17
  export * from "./multiplex.js";
15
18
  export * from "./partition.js";
19
+ export * from "./pubsub.js";
16
20
  export * from "./push.js";
17
21
  export * from "./raf.js";
18
22
  export * from "./range.js";
@@ -20,8 +24,10 @@ export * from "./reduce.js";
20
24
  export * from "./repeatedly.js";
21
25
  export * from "./run.js";
22
26
  export * from "./sidechain.js";
27
+ export * from "./source.js";
23
28
  export * from "./step.js";
24
29
  export * from "./sync.js";
30
+ export * from "./sync-raf.js";
25
31
  export * from "./take.js";
26
32
  export * from "./throttle-time.js";
27
33
  export * from "./throttle.js";
package/mult.d.ts ADDED
@@ -0,0 +1,83 @@
1
+ import type { Fn, Fn0, Maybe } from "@thi.ng/api";
2
+ /**
3
+ * Creates a new {@link Mult} instance which allows splitting a single `src`
4
+ * async iterable into multiple parallel subscribers. Iteration only starts when
5
+ * the first subscriber is attached (via {@link Mult.subscribe}) and back
6
+ * pressure is handled by waiting for **all** child subscribers to deliver their
7
+ * values before the next value from `src` is consumed. `Mult` allows dynamic
8
+ * subscriptions and unsubscriptions and will stop consuming from `src` when no
9
+ * further subscribers are attached.
10
+ *
11
+ * @example
12
+ * ```ts tangle:../export/mult.ts
13
+ * import { map, mult, run, wait } from "@thi.ng/transducers-async";
14
+ *
15
+ * const root = mult(
16
+ * (async function* () {
17
+ * yield "hello";
18
+ * await wait(1000);
19
+ * yield "world";
20
+ * await wait(1000);
21
+ * yield "good bye";
22
+ * })()
23
+ * );
24
+ *
25
+ * // 1st subscriber (vanilla JS)
26
+ * (async () => {
27
+ * for await (let x of root.subscribe()) console.log("vanilla:", x);
28
+ * })();
29
+ *
30
+ * // 2nd subscriber (transducer), attached with delay
31
+ * setTimeout(
32
+ * () =>
33
+ * run(
34
+ * map(async (x) => {
35
+ * console.log("tx", x);
36
+ * await wait(1500);
37
+ * }),
38
+ * root.subscribe()
39
+ * ),
40
+ * 900
41
+ * );
42
+ *
43
+ * // vanilla: hello
44
+ * // vanilla: world
45
+ * // tx world
46
+ * // vanilla: good bye
47
+ * // tx good bye
48
+ * ```
49
+ *
50
+ * @param src
51
+ */
52
+ export declare const mult: <T>(src: AsyncIterable<T>) => Mult<T>;
53
+ export declare class Mult<T> {
54
+ src: AsyncIterable<T>;
55
+ protected subs: MSub<T>[];
56
+ protected isActive: boolean;
57
+ constructor(src: AsyncIterable<T>);
58
+ /**
59
+ * Creates a new subscription (aka custom `AsyncIterable`) which will
60
+ * receive any future values from `src`. The returned subscription can be
61
+ * removed again via {@link Mult.unsubscribe}.
62
+ */
63
+ subscribe(): AsyncIterable<T>;
64
+ /**
65
+ * Attempts to remove given child subscription (presumably created via
66
+ * {@link Mult.subscribe}). Returns true if removal was successful.
67
+ *
68
+ * @param sub
69
+ */
70
+ unsubscribe(sub: AsyncIterable<T>): boolean;
71
+ }
72
+ /** @internal */
73
+ export declare class MSub<T> {
74
+ valueP: Promise<Maybe<T>>;
75
+ notifyP: Promise<void>;
76
+ resolve: Fn<Maybe<T>, void>;
77
+ notify: Fn0<void>;
78
+ active: boolean;
79
+ constructor();
80
+ [Symbol.asyncIterator](): AsyncGenerator<Awaited<Awaited<T> & null> | Awaited<Awaited<T> & {}>, void, unknown>;
81
+ protected $await(): void;
82
+ }
83
+ //# sourceMappingURL=mult.d.ts.map
package/mult.js ADDED
@@ -0,0 +1,85 @@
1
+ import { illegalState } from "@thi.ng/errors/illegal-state";
2
+ const mult = (src) => new Mult(src);
3
+ class Mult {
4
+ constructor(src) {
5
+ this.src = src;
6
+ }
7
+ subs = [];
8
+ isActive = false;
9
+ /**
10
+ * Creates a new subscription (aka custom `AsyncIterable`) which will
11
+ * receive any future values from `src`. The returned subscription can be
12
+ * removed again via {@link Mult.unsubscribe}.
13
+ */
14
+ subscribe() {
15
+ const sub = new MSub();
16
+ this.subs.push(sub);
17
+ if (!this.isActive) {
18
+ this.isActive = true;
19
+ (async () => {
20
+ for await (let val of this.src) {
21
+ for (let s of this.subs)
22
+ s.resolve(val);
23
+ if (val === void 0)
24
+ this.subs.length = 0;
25
+ if (!this.subs.length)
26
+ break;
27
+ await Promise.all(this.subs.map((x) => x.notifyP));
28
+ }
29
+ for (let s of this.subs)
30
+ s.resolve(void 0);
31
+ this.subs.length = 0;
32
+ this.isActive = false;
33
+ })();
34
+ }
35
+ return sub;
36
+ }
37
+ /**
38
+ * Attempts to remove given child subscription (presumably created via
39
+ * {@link Mult.subscribe}). Returns true if removal was successful.
40
+ *
41
+ * @param sub
42
+ */
43
+ unsubscribe(sub) {
44
+ const idx = this.subs.findIndex((x) => x === sub);
45
+ if (idx >= 0) {
46
+ this.subs.splice(idx, 1);
47
+ sub.resolve(void 0);
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+ }
53
+ class MSub {
54
+ valueP;
55
+ notifyP;
56
+ resolve;
57
+ notify;
58
+ active = false;
59
+ constructor() {
60
+ this.$await();
61
+ }
62
+ async *[Symbol.asyncIterator]() {
63
+ if (this.active)
64
+ illegalState("multiple consumers unsupported");
65
+ this.active = true;
66
+ while (true) {
67
+ const res = await this.valueP;
68
+ if (res === void 0)
69
+ break;
70
+ yield res;
71
+ this.notify();
72
+ this.$await();
73
+ }
74
+ this.active = false;
75
+ }
76
+ $await() {
77
+ this.notifyP = new Promise((res) => this.notify = res);
78
+ this.valueP = new Promise((res) => this.resolve = res);
79
+ }
80
+ }
81
+ export {
82
+ MSub,
83
+ Mult,
84
+ mult
85
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/transducers-async",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "Async versions of various highly composable transducers, reducers and iterators",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -36,11 +36,12 @@
36
36
  "tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
37
37
  },
38
38
  "dependencies": {
39
- "@thi.ng/api": "^8.10.1",
40
- "@thi.ng/checks": "^3.6.1",
41
- "@thi.ng/compose": "^3.0.1",
42
- "@thi.ng/errors": "^2.5.4",
43
- "@thi.ng/transducers": "^9.0.1"
39
+ "@thi.ng/api": "^8.11.0",
40
+ "@thi.ng/buffers": "^0.1.1",
41
+ "@thi.ng/checks": "^3.6.2",
42
+ "@thi.ng/compose": "^3.0.2",
43
+ "@thi.ng/errors": "^2.5.5",
44
+ "@thi.ng/transducers": "^9.0.2"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@microsoft/api-extractor": "^7.43.0",
@@ -89,6 +90,9 @@
89
90
  "./api": {
90
91
  "default": "./api.js"
91
92
  },
93
+ "./as-async": {
94
+ "default": "./as-async.js"
95
+ },
92
96
  "./cat": {
93
97
  "default": "./cat.js"
94
98
  },
@@ -101,6 +105,9 @@
101
105
  "./concat": {
102
106
  "default": "./concat.js"
103
107
  },
108
+ "./delayed": {
109
+ "default": "./delayed.js"
110
+ },
104
111
  "./ensure": {
105
112
  "default": "./ensure.js"
106
113
  },
@@ -122,6 +129,9 @@
122
129
  "./merge": {
123
130
  "default": "./merge.js"
124
131
  },
132
+ "./mult": {
133
+ "default": "./mult.js"
134
+ },
125
135
  "./multiplex-obj": {
126
136
  "default": "./multiplex-obj.js"
127
137
  },
@@ -131,6 +141,9 @@
131
141
  "./partition": {
132
142
  "default": "./partition.js"
133
143
  },
144
+ "./pubsub": {
145
+ "default": "./pubsub.js"
146
+ },
134
147
  "./push": {
135
148
  "default": "./push.js"
136
149
  },
@@ -152,9 +165,15 @@
152
165
  "./sidechain": {
153
166
  "default": "./sidechain.js"
154
167
  },
168
+ "./source": {
169
+ "default": "./source.js"
170
+ },
155
171
  "./step": {
156
172
  "default": "./step.js"
157
173
  },
174
+ "./sync-raf": {
175
+ "default": "./sync-raf.js"
176
+ },
158
177
  "./sync": {
159
178
  "default": "./sync.js"
160
179
  },
@@ -180,5 +199,5 @@
180
199
  "status": "alpha",
181
200
  "year": 2018
182
201
  },
183
- "gitHead": "fd7ab12d565809a2300783881c62b883dbd25c23\n"
202
+ "gitHead": "4e2bf6b12e35192e678b525fa699429370e357d3\n"
184
203
  }
package/pubsub.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import type { Fn } from "@thi.ng/api";
2
+ import { MSub } from "./mult.js";
3
+ export declare const pubsub: <K, V>(src: AsyncIterable<V>, topicFn: Fn<V, K>) => PubSub<K, V>;
4
+ export declare class PubSub<K, V> {
5
+ src: AsyncIterable<V>;
6
+ topicFn: Fn<V, K>;
7
+ protected topics: Map<K, MSub<V>[]>;
8
+ protected isActive: boolean;
9
+ constructor(src: AsyncIterable<V>, topicFn: Fn<V, K>);
10
+ /**
11
+ * Creates a new subscription (aka custom `AsyncIterable`) which will
12
+ * receive any future values from `src` matching given topic `id`. The
13
+ * returned subscription can be removed again via
14
+ * {@link PubSub.unsubscribeTopic}.
15
+ */
16
+ subscribeTopic(id: K): AsyncIterable<V>;
17
+ /**
18
+ * Attempts to remove given child subscription (presumably created via
19
+ * {@link PubSub.subscribeTopic}). Returns true if removal was successful.
20
+ *
21
+ * @param sub
22
+ */
23
+ unsubscribeTopic(id: K, sub: AsyncIterable<V>): boolean;
24
+ protected process(): Promise<void>;
25
+ }
26
+ //# sourceMappingURL=pubsub.d.ts.map
package/pubsub.js ADDED
@@ -0,0 +1,68 @@
1
+ import { MSub } from "./mult.js";
2
+ const pubsub = (src, topicFn) => new PubSub(src, topicFn);
3
+ class PubSub {
4
+ constructor(src, topicFn) {
5
+ this.src = src;
6
+ this.topicFn = topicFn;
7
+ }
8
+ topics = /* @__PURE__ */ new Map();
9
+ isActive = false;
10
+ /**
11
+ * Creates a new subscription (aka custom `AsyncIterable`) which will
12
+ * receive any future values from `src` matching given topic `id`. The
13
+ * returned subscription can be removed again via
14
+ * {@link PubSub.unsubscribeTopic}.
15
+ */
16
+ subscribeTopic(id) {
17
+ const sub = new MSub();
18
+ let subs = this.topics.get(id);
19
+ if (!subs) {
20
+ this.topics.set(id, subs = []);
21
+ }
22
+ subs.push(sub);
23
+ if (!this.isActive) {
24
+ this.isActive = true;
25
+ this.process();
26
+ }
27
+ return sub;
28
+ }
29
+ /**
30
+ * Attempts to remove given child subscription (presumably created via
31
+ * {@link PubSub.subscribeTopic}). Returns true if removal was successful.
32
+ *
33
+ * @param sub
34
+ */
35
+ unsubscribeTopic(id, sub) {
36
+ const subs = this.topics.get(id);
37
+ if (!subs)
38
+ return false;
39
+ const idx = subs.findIndex((x) => x === sub) ?? -1;
40
+ if (idx >= 0) {
41
+ subs.splice(idx, 1);
42
+ sub.resolve(void 0);
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+ async process() {
48
+ for await (let val of this.src) {
49
+ const topic = this.topicFn(val);
50
+ const subs = this.topics.get(topic);
51
+ if (!(subs && subs.length))
52
+ continue;
53
+ for (let s of subs)
54
+ s.resolve(val);
55
+ await Promise.all(subs.map((x) => x.notifyP));
56
+ }
57
+ for (let subs of this.topics.values()) {
58
+ for (let s of subs)
59
+ s.resolve(void 0);
60
+ }
61
+ this.topics.clear();
62
+ this.isActive = false;
63
+ }
64
+ }
65
+ export {
66
+ PubSub,
67
+ pubsub
68
+ };
package/raf.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { IDeref, Maybe } from "@thi.ng/api";
2
+ import type { ClosableAsyncGenerator } from "./api.js";
1
3
  export interface RAFOpts {
2
4
  /**
3
5
  * If true (default: false), passes the timestamps received
@@ -16,5 +18,5 @@ export interface RAFOpts {
16
18
  */
17
19
  t0: number | boolean;
18
20
  }
19
- export declare function raf(opts?: Partial<RAFOpts>): AsyncGenerator<number>;
21
+ export declare const raf: (opts?: Partial<RAFOpts>) => ClosableAsyncGenerator<number> & IDeref<Maybe<number>>;
20
22
  //# sourceMappingURL=raf.d.ts.map
package/raf.js CHANGED
@@ -1,11 +1,16 @@
1
- async function* raf(opts) {
1
+ import { source } from "./source.js";
2
+ const raf = (opts) => {
2
3
  let frame = 0;
3
4
  let t0 = opts?.t0 || 0;
4
- while (true) {
5
- let resolve;
6
- const promise = new Promise(($resolve) => resolve = $resolve);
7
- requestAnimationFrame(resolve);
8
- let t = await promise;
5
+ let isClosed = false;
6
+ const gen = source();
7
+ gen.close = () => {
8
+ isClosed = true;
9
+ gen.write(void 0);
10
+ };
11
+ const update = (t) => {
12
+ if (isClosed)
13
+ return;
9
14
  if (opts?.timestamp) {
10
15
  if (t0 === true)
11
16
  t0 = t;
@@ -14,11 +19,12 @@ async function* raf(opts) {
14
19
  } else {
15
20
  t = frame++;
16
21
  }
17
- const cancel = yield t;
18
- if (cancel === true)
19
- break;
20
- }
21
- }
22
+ gen.write(t);
23
+ requestAnimationFrame(update);
24
+ };
25
+ requestAnimationFrame(update);
26
+ return gen;
27
+ };
22
28
  export {
23
29
  raf
24
30
  };
package/range.js CHANGED
@@ -1,10 +1,10 @@
1
- import { delayed } from "@thi.ng/compose/delayed";
2
1
  import { Range } from "@thi.ng/transducers/range";
2
+ import { wait } from "./delayed.js";
3
3
  async function* range(...args) {
4
4
  const delay = args.pop();
5
5
  for (let x of new Range(...args)) {
6
6
  yield x;
7
- await delayed(null, delay);
7
+ await wait(delay);
8
8
  }
9
9
  }
10
10
  export {
package/repeatedly.js CHANGED
@@ -1,9 +1,9 @@
1
- import { delayed } from "@thi.ng/compose/delayed";
1
+ import { wait } from "./delayed.js";
2
2
  async function* repeatedly(fn, n = Infinity, delay = 0) {
3
3
  for (let i = 0; i < n; i++) {
4
4
  yield await fn(i);
5
5
  if (delay > 0) {
6
- await delayed(null, delay);
6
+ await wait(delay);
7
7
  }
8
8
  }
9
9
  }
package/sidechain.d.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  export interface SidechainOpts {
2
2
  /**
3
3
  * If true (default), only emits the last received value when the sidechain
4
- * triggers. Otherwise buffers and emits *all* received values since the
5
- * last time the sidechain triggered.
4
+ * delivers a truthy value. Otherwise buffers and emits *all* received
5
+ * values since the last time the sidechain triggered.
6
6
  *
7
7
  * @defaultValue true
8
8
  */
9
9
  lastOnly: boolean;
10
10
  }
11
- export declare function sidechain<T>(src: AsyncIterable<T>, side: AsyncIterable<boolean>, opts: Partial<SidechainOpts> & {
11
+ export declare function sidechain<T>(src: AsyncIterable<T>, side: AsyncIterable<any>, opts: Partial<SidechainOpts> & {
12
12
  lastOnly: false;
13
13
  }): AsyncIterableIterator<T[]>;
14
- export declare function sidechain<T>(src: AsyncIterable<T>, side: AsyncIterable<boolean>, opts?: Partial<SidechainOpts>): AsyncIterableIterator<T>;
14
+ export declare function sidechain<T>(src: AsyncIterable<T>, side: AsyncIterable<any>, opts?: Partial<SidechainOpts>): AsyncIterableIterator<T>;
15
15
  //# sourceMappingURL=sidechain.d.ts.map
package/source.d.ts ADDED
@@ -0,0 +1,93 @@
1
+ import type { FnO, IDeref, Maybe } from "@thi.ng/api";
2
+ import type { IReadWriteBuffer } from "@thi.ng/buffers";
3
+ import type { ClosableAsyncGenerator } from "./api.js";
4
+ export interface Source<T> extends ClosableAsyncGenerator<T>, IDeref<Maybe<T>> {
5
+ /**
6
+ * Writes or queues given value `x` to be asynchronously passed down to the
7
+ * source's consumer. If no other values are queued yet, the source
8
+ * processes it immediately, otherwise the value will be first added to the
9
+ * source's configured buffer.
10
+ *
11
+ * @remarks
12
+ * Once {@link Source.close} has been called, all future write attempts will
13
+ * be silently ignored.
14
+ *
15
+ * @param x
16
+ */
17
+ write(x?: T): void;
18
+ /**
19
+ * Returns the most recently produced/yielded value (if any). If there's
20
+ * back pressure (i.e. queued values caused by more frequent writes and than
21
+ * reads, the returned value is **not** necessarily the last value written
22
+ * via {@link Source.write}). Returns `undefined`, if no value has yet been
23
+ * written or the source has already been fully closed (via
24
+ * {@link Source.close}).
25
+ */
26
+ deref(): Maybe<T>;
27
+ /**
28
+ * Takes a function which will be called with the most recent emitted value
29
+ * of the source (plus any optionally given args) and queues the result of
30
+ * that function as new value to be asynchronously passed down by the source
31
+ * to its consumer.
32
+ *
33
+ * @remarks
34
+ * Only use this function in situations where there's no back pressure (i.e.
35
+ * no queued up values).
36
+ *
37
+ * @param fn
38
+ * @param args
39
+ */
40
+ update(fn: FnO<Maybe<T>, Maybe<T>>, ...args: any[]): void;
41
+ }
42
+ /**
43
+ * Creates an async iterable acting as data source with an extended API to
44
+ * externally feed/write new values, which are then passed downstream to any
45
+ * attached processors/transducers. The source can use an optional buffer
46
+ * implementation to control back pressure behavior and value ordering.
47
+ *
48
+ * @remarks
49
+ * See [thi.ng/buffers](https://thi.ng/buffers) for available buffer
50
+ * implementations. By default a
51
+ * [`fifo()`](https://docs.thi.ng/umbrella/buffers/functions/fifo.html) buffer
52
+ * with capacity=1 is used.
53
+ *
54
+ * If `initial` is given, the source will immediately deliver this value once a
55
+ * consumer is attached.
56
+ *
57
+ * The `source()` stores the last produced/yielded value (not necessarily the
58
+ * last value written via {@link Source.write}), which can be read via
59
+ * {@link Source.deref} or updated via {@link Source.update}.
60
+ *
61
+ * @example
62
+ * ```ts tangle:../export/source.ts
63
+ * import { source, map, run } from "@thi.ng/transducers-async";
64
+ *
65
+ * // create empty source
66
+ * const src = source();
67
+ *
68
+ * // create an async consumer
69
+ * // (consumer stops when we close the source)
70
+ * run(
71
+ * map(async (x) => x * 10),
72
+ * (x) => console.log("result:", x),
73
+ * src
74
+ * )
75
+ *
76
+ * // set new value
77
+ * src.write(23);
78
+ * // result: 230
79
+ *
80
+ * // update last value
81
+ * // (delayed invocation here to avoid buffer overflow)
82
+ * setTimeout(() => src.update((x) => x + 1), 0);
83
+ * // result: 240
84
+ *
85
+ * // close/terminate source
86
+ * setTimeout(() => src.close(), 0);
87
+ * ```
88
+ *
89
+ * @param initial
90
+ * @param buffer
91
+ */
92
+ export declare const source: <T>(initial?: T, buffer?: IReadWriteBuffer<Maybe<T>> | number) => Source<T>;
93
+ //# sourceMappingURL=source.d.ts.map
package/source.js ADDED
@@ -0,0 +1,51 @@
1
+ import { fifo } from "@thi.ng/buffers/fifo";
2
+ import { isNumber } from "@thi.ng/checks/is-number";
3
+ import { illegalState } from "@thi.ng/errors/illegal-state";
4
+ const source = (initial, buffer = 1) => {
5
+ const queue = isNumber(buffer) ? fifo(buffer) : buffer;
6
+ let last = initial;
7
+ let state = 0;
8
+ let promise;
9
+ let resolve;
10
+ const newPromise = () => {
11
+ promise = new Promise(($resolve) => {
12
+ resolve = $resolve;
13
+ });
14
+ };
15
+ newPromise();
16
+ const gen = async function* () {
17
+ while (true) {
18
+ const val = await promise;
19
+ last = val;
20
+ if (val === void 0)
21
+ break;
22
+ yield val;
23
+ newPromise();
24
+ if (queue.readable())
25
+ resolve(queue.read());
26
+ }
27
+ state = 2;
28
+ }();
29
+ gen.write = (x) => {
30
+ if (state > 0)
31
+ return;
32
+ if (resolve) {
33
+ resolve(x);
34
+ resolve = void 0;
35
+ } else if (queue.writable()) {
36
+ queue.write(x);
37
+ if (x === void 0)
38
+ state = 1;
39
+ } else
40
+ illegalState("buffer overflow");
41
+ };
42
+ gen.update = (fn, ...args) => gen.write(fn(last, ...args));
43
+ gen.close = () => gen.write(void 0);
44
+ gen.deref = () => last;
45
+ if (initial !== void 0)
46
+ gen.write(initial);
47
+ return gen;
48
+ };
49
+ export {
50
+ source
51
+ };
package/step.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Fn } from "@thi.ng/api";
1
+ import type { Fn, Maybe } from "@thi.ng/api";
2
2
  import type { AsyncTxLike } from "./api.js";
3
- export declare const step: <A, B>(tx: AsyncTxLike<A, B>, unwrap?: boolean) => Fn<A, Promise<B | B[] | undefined>>;
3
+ export declare const step: <A, B>(tx: AsyncTxLike<A, B>, unwrap?: boolean) => Fn<A, Promise<Maybe<B | B[]>>>;
4
4
  //# sourceMappingURL=step.d.ts.map
package/step.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { isReduced } from "@thi.ng/transducers/reduced";
1
2
  import { ensureAsyncTransducer } from "./ensure.js";
2
3
  import { push } from "./push.js";
3
- import { isReduced } from "@thi.ng/transducers/reduced";
4
4
  const step = (tx, unwrap = true) => {
5
5
  const [_, complete, reduce] = ensureAsyncTransducer(tx)(push());
6
6
  let done = false;
package/sync-raf.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const syncRAF: <T>(src: AsyncIterable<T>) => AsyncIterableIterator<T>;
2
+ //# sourceMappingURL=sync-raf.d.ts.map
package/sync-raf.js ADDED
@@ -0,0 +1,6 @@
1
+ import { raf } from "./raf.js";
2
+ import { sidechain } from "./sidechain.js";
3
+ const syncRAF = (src) => sidechain(src, raf({ timestamp: true }));
4
+ export {
5
+ syncRAF
6
+ };