@norskvideo/moq-net 0.1.8 → 0.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/README.md +2 -2
- package/announce.d.ts +7 -0
- package/announce.d.ts.map +1 -0
- package/announce.js +8 -0
- package/announce.js.map +1 -0
- package/announced.d.ts +49 -91
- package/announced.d.ts.map +1 -1
- package/announced.js +21 -156
- package/announced.js.map +1 -1
- package/bandwidth.d.ts +163 -0
- package/bandwidth.d.ts.map +1 -0
- package/bandwidth.js +304 -0
- package/bandwidth.js.map +1 -0
- package/bandwidth_api.d.ts +7 -0
- package/bandwidth_api.d.ts.map +1 -0
- package/bandwidth_api.js +8 -0
- package/bandwidth_api.js.map +1 -0
- package/broadcast.d.ts +44 -35
- package/broadcast.d.ts.map +1 -1
- package/broadcast.js +104 -60
- package/broadcast.js.map +1 -1
- package/connection/accept.d.ts +16 -1
- package/connection/accept.d.ts.map +1 -1
- package/connection/accept.js +52 -28
- package/connection/accept.js.map +1 -1
- package/connection/browser.d.ts.map +1 -1
- package/connection/browser.js +9 -7
- package/connection/browser.js.map +1 -1
- package/connection/connect.d.ts +30 -6
- package/connection/connect.d.ts.map +1 -1
- package/connection/connect.js +110 -54
- package/connection/connect.js.map +1 -1
- package/connection/established.d.ts +17 -21
- package/connection/established.d.ts.map +1 -1
- package/connection/established.js.map +1 -1
- package/connection/forward.d.ts +2 -0
- package/connection/forward.d.ts.map +1 -0
- package/connection/forward.js +173 -0
- package/connection/forward.js.map +1 -0
- package/connection/handshake.d.ts +1 -0
- package/connection/handshake.d.ts.map +1 -1
- package/connection/handshake.js +5 -2
- package/connection/handshake.js.map +1 -1
- package/connection/index.d.ts +5 -5
- package/connection/index.d.ts.map +1 -1
- package/connection/index.js +4 -5
- package/connection/index.js.map +1 -1
- package/connection/pool.d.ts +186 -0
- package/connection/pool.d.ts.map +1 -0
- package/connection/pool.js +361 -0
- package/connection/pool.js.map +1 -0
- package/connection/reload.d.ts +14 -93
- package/connection/reload.d.ts.map +1 -1
- package/connection/reload.js +217 -81
- package/connection/reload.js.map +1 -1
- package/connection/stats.d.ts +3 -26
- package/connection/stats.d.ts.map +1 -1
- package/connection/stats.js.map +1 -1
- package/connection/transport.d.ts +0 -7
- package/connection/transport.d.ts.map +1 -1
- package/consume.d.ts +1 -43
- package/consume.d.ts.map +1 -1
- package/consume.js +1 -1
- package/consume.js.map +1 -1
- package/error.d.ts +180 -29
- package/error.d.ts.map +1 -1
- package/error.js +331 -16
- package/error.js.map +1 -1
- package/errors.d.ts +7 -0
- package/errors.d.ts.map +1 -0
- package/errors.js +8 -0
- package/errors.js.map +1 -0
- package/group.d.ts +9 -43
- package/group.d.ts.map +1 -1
- package/group.js +284 -69
- package/group.js.map +1 -1
- package/hop.d.ts +115 -0
- package/hop.d.ts.map +1 -0
- package/hop.js +119 -0
- package/hop.js.map +1 -0
- package/ietf/adapter.d.ts +5 -1
- package/ietf/adapter.d.ts.map +1 -1
- package/ietf/adapter.js +105 -60
- package/ietf/adapter.js.map +1 -1
- package/ietf/aliases.d.ts +1 -78
- package/ietf/aliases.d.ts.map +1 -1
- package/ietf/cluster.d.ts +9 -123
- package/ietf/cluster.d.ts.map +1 -1
- package/ietf/cluster.js +84 -44
- package/ietf/cluster.js.map +1 -1
- package/ietf/connection.d.ts +6 -72
- package/ietf/connection.d.ts.map +1 -1
- package/ietf/connection.js +62 -56
- package/ietf/connection.js.map +1 -1
- package/ietf/error.d.ts +11 -0
- package/ietf/error.d.ts.map +1 -0
- package/ietf/error.js +193 -0
- package/ietf/error.js.map +1 -0
- package/ietf/fetch.d.ts +7 -20
- package/ietf/fetch.d.ts.map +1 -1
- package/ietf/fetch.js +52 -22
- package/ietf/fetch.js.map +1 -1
- package/ietf/filter.d.ts +2 -0
- package/ietf/filter.d.ts.map +1 -1
- package/ietf/filter.js +10 -1
- package/ietf/filter.js.map +1 -1
- package/ietf/goaway.d.ts.map +1 -1
- package/ietf/goaway.js +22 -5
- package/ietf/goaway.js.map +1 -1
- package/ietf/hidden.d.ts +2 -0
- package/ietf/hidden.d.ts.map +1 -0
- package/ietf/hidden.js +30 -0
- package/ietf/hidden.js.map +1 -0
- package/ietf/index.d.ts +2 -0
- package/ietf/index.d.ts.map +1 -1
- package/ietf/index.js +2 -0
- package/ietf/index.js.map +1 -1
- package/ietf/object.d.ts +15 -8
- package/ietf/object.d.ts.map +1 -1
- package/ietf/object.js +51 -36
- package/ietf/object.js.map +1 -1
- package/ietf/parameters.d.ts +14 -2
- package/ietf/parameters.d.ts.map +1 -1
- package/ietf/parameters.js +97 -29
- package/ietf/parameters.js.map +1 -1
- package/ietf/properties.d.ts +1 -0
- package/ietf/properties.d.ts.map +1 -1
- package/ietf/properties.js +14 -0
- package/ietf/properties.js.map +1 -1
- package/ietf/publish.d.ts +19 -2
- package/ietf/publish.d.ts.map +1 -1
- package/ietf/publish.js +40 -6
- package/ietf/publish.js.map +1 -1
- package/ietf/publish_namespace.d.ts +25 -0
- package/ietf/publish_namespace.d.ts.map +1 -1
- package/ietf/publish_namespace.js +63 -0
- package/ietf/publish_namespace.js.map +1 -1
- package/ietf/publisher.d.ts +1 -82
- package/ietf/publisher.d.ts.map +1 -1
- package/ietf/publisher.js +483 -235
- package/ietf/publisher.js.map +1 -1
- package/ietf/solicit.d.ts +1 -40
- package/ietf/solicit.d.ts.map +1 -1
- package/ietf/subscribe.d.ts +8 -6
- package/ietf/subscribe.d.ts.map +1 -1
- package/ietf/subscribe.js +33 -27
- package/ietf/subscribe.js.map +1 -1
- package/ietf/subscribe_namespace.d.ts +8 -2
- package/ietf/subscribe_namespace.d.ts.map +1 -1
- package/ietf/subscribe_namespace.js +18 -8
- package/ietf/subscribe_namespace.js.map +1 -1
- package/ietf/subscriber.d.ts +1 -65
- package/ietf/subscriber.d.ts.map +1 -1
- package/ietf/subscriber.js +337 -123
- package/ietf/subscriber.js.map +1 -1
- package/ietf/token.d.ts +2 -0
- package/ietf/token.d.ts.map +1 -0
- package/ietf/token.js +99 -0
- package/ietf/token.js.map +1 -0
- package/ietf/track.d.ts +4 -0
- package/ietf/track.d.ts.map +1 -1
- package/ietf/track.js +6 -20
- package/ietf/track.js.map +1 -1
- package/ietf/version.d.ts +12 -1
- package/ietf/version.d.ts.map +1 -1
- package/ietf/version.js +13 -0
- package/ietf/version.js.map +1 -1
- package/index.d.ts +12 -7
- package/index.d.ts.map +1 -1
- package/index.js +10 -5
- package/index.js.map +1 -1
- package/internal.d.ts +115 -1
- package/internal.d.ts.map +1 -1
- package/internal.js +108 -0
- package/internal.js.map +1 -1
- package/lite/announce.d.ts +60 -10
- package/lite/announce.d.ts.map +1 -1
- package/lite/announce.js +176 -31
- package/lite/announce.js.map +1 -1
- package/lite/connection.d.ts +9 -59
- package/lite/connection.d.ts.map +1 -1
- package/lite/connection.js +34 -35
- package/lite/connection.js.map +1 -1
- package/lite/datagram.d.ts +3 -2
- package/lite/datagram.d.ts.map +1 -1
- package/lite/datagram.js +7 -8
- package/lite/datagram.js.map +1 -1
- package/lite/fetch.d.ts +15 -1
- package/lite/fetch.d.ts.map +1 -1
- package/lite/fetch.js +39 -7
- package/lite/fetch.js.map +1 -1
- package/lite/goaway.d.ts.map +1 -1
- package/lite/goaway.js +7 -1
- package/lite/goaway.js.map +1 -1
- package/lite/group.d.ts +30 -12
- package/lite/group.d.ts.map +1 -1
- package/lite/group.js +68 -26
- package/lite/group.js.map +1 -1
- package/lite/message.d.ts +2 -2
- package/lite/message.d.ts.map +1 -1
- package/lite/message.js +14 -5
- package/lite/message.js.map +1 -1
- package/lite/priority.d.ts +1 -61
- package/lite/priority.d.ts.map +1 -1
- package/lite/priority.js +4 -5
- package/lite/priority.js.map +1 -1
- package/lite/publisher.d.ts +1 -69
- package/lite/publisher.d.ts.map +1 -1
- package/lite/publisher.js +630 -262
- package/lite/publisher.js.map +1 -1
- package/lite/setup.d.ts +8 -8
- package/lite/setup.d.ts.map +1 -1
- package/lite/setup.js +33 -30
- package/lite/setup.js.map +1 -1
- package/lite/subscribe.d.ts +71 -17
- package/lite/subscribe.d.ts.map +1 -1
- package/lite/subscribe.js +205 -53
- package/lite/subscribe.js.map +1 -1
- package/lite/subscriber.d.ts +15 -56
- package/lite/subscriber.d.ts.map +1 -1
- package/lite/subscriber.js +451 -247
- package/lite/subscriber.js.map +1 -1
- package/lite/track.d.ts +4 -10
- package/lite/track.d.ts.map +1 -1
- package/lite/track.js +34 -29
- package/lite/track.js.map +1 -1
- package/lite/version.d.ts +42 -6
- package/lite/version.d.ts.map +1 -1
- package/lite/version.js +126 -10
- package/lite/version.js.map +1 -1
- package/origin.d.ts +256 -29
- package/origin.d.ts.map +1 -1
- package/origin.js +1427 -37
- package/origin.js.map +1 -1
- package/package.json +8 -3
- package/path.d.ts +25 -7
- package/path.d.ts.map +1 -1
- package/path.js +5 -3
- package/path.js.map +1 -1
- package/stream.d.ts +73 -14
- package/stream.d.ts.map +1 -1
- package/stream.js +372 -141
- package/stream.js.map +1 -1
- package/tail.d.ts +18 -0
- package/tail.d.ts.map +1 -0
- package/tail.js +167 -0
- package/tail.js.map +1 -0
- package/time.d.ts +15 -2
- package/time.d.ts.map +1 -1
- package/time.js +28 -9
- package/time.js.map +1 -1
- package/track.d.ts +211 -83
- package/track.d.ts.map +1 -1
- package/track.js +816 -205
- package/track.js.map +1 -1
- package/util/abort.d.ts +2 -0
- package/util/abort.d.ts.map +1 -0
- package/util/abort.js +20 -0
- package/util/abort.js.map +1 -0
- package/util/log.d.ts +5 -0
- package/util/log.d.ts.map +1 -0
- package/util/log.js +17 -0
- package/util/log.js.map +1 -0
- package/util/u64.d.ts +39 -0
- package/util/u64.d.ts.map +1 -0
- package/util/u64.js +83 -0
- package/util/u64.js.map +1 -0
- package/util/varint.d.ts +29 -0
- package/util/varint.d.ts.map +1 -0
- package/util/varint.js +198 -0
- package/util/varint.js.map +1 -0
- package/varint.d.ts +10 -6
- package/varint.d.ts.map +1 -1
- package/varint.js +40 -237
- package/varint.js.map +1 -1
- package/wire.d.ts +80 -0
- package/wire.d.ts.map +1 -0
- package/wire.js +32 -0
- package/wire.js.map +1 -0
- package/zod.d.ts +1 -1
- package/zod.d.ts.map +1 -1
- package/zod.js.map +1 -1
- package/mock.d.ts +0 -66
- package/mock.d.ts.map +0 -1
- package/mock.js +0 -243
- package/mock.js.map +0 -1
package/README.md
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
[](https://www.typescriptlang.org/)
|
|
9
9
|
|
|
10
10
|
A TypeScript [Media over QUIC](https://moq.dev/) (MoQ) client for both browsers and server JS/TS environments.
|
|
11
|
-
`@moq/net` is the **networking layer**: real-time pub/sub with built-in caching, fan-out, and prioritization, on top of QUIC. At session setup it negotiates one of two wire protocols, either the simplified [moq-lite](https://doc.moq.dev/concept/
|
|
11
|
+
`@moq/net` is the **networking layer**: real-time pub/sub with built-in caching, fan-out, and prioritization, on top of QUIC. At session setup it negotiates one of two wire protocols, either the simplified [moq-lite](https://doc.moq.dev/concept/moq-lite) protocol or the full IETF [moq-transport](https://datatracker.ietf.org/doc/draft-ietf-moq-transport/) draft.
|
|
12
12
|
|
|
13
13
|
Check out [hang](../hang) for a higher-level media library that uses this package.
|
|
14
14
|
|
|
15
|
-
> **Note:** moq-lite is a subset of moq-transport and is forwards compatible with it, so this client works with any moq-transport CDN (ex. [Cloudflare](https://moq.dev/blog/first-cdn/)). See the [compatibility docs](https://doc.moq.dev/concept/
|
|
15
|
+
> **Note:** moq-lite is a subset of moq-transport and is forwards compatible with it, so this client works with any moq-transport CDN (ex. [Cloudflare](https://moq.dev/blog/first-cdn/)). See the [compatibility docs](https://doc.moq.dev/concept/moq-lite#compatibility) for details.
|
|
16
16
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
package/announce.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announce.d.ts","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC"}
|
package/announce.js
ADDED
package/announce.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announce.js","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAA0B,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * Broadcast announcement streams.\n *\n * @module\n */\nexport { Consumer, isActive, type Kind, type Update } from \"./announced.ts\";\n"]}
|
package/announced.d.ts
CHANGED
|
@@ -1,23 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Broadcast announcement streams: which broadcast paths are available under a
|
|
2
|
+
* Broadcast announcement streams: which broadcast paths are available under a scope.
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import { type GetPromise
|
|
7
|
-
import type
|
|
8
|
-
import type
|
|
9
|
-
import * as Path from "./path.js";
|
|
6
|
+
import { type GetPromise } from "@norskvideo/moq-signals";
|
|
7
|
+
import type { Route } from "./hop.js";
|
|
8
|
+
import type * as Path from "./path.js";
|
|
10
9
|
/**
|
|
11
|
-
*
|
|
10
|
+
* What an {@link Update} reports about its prefix.
|
|
12
11
|
*
|
|
13
12
|
* @public
|
|
14
13
|
*/
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
export type Kind = "announced" | "updated" | "retracted";
|
|
15
|
+
/**
|
|
16
|
+
* A route announcement, update, or retraction.
|
|
17
|
+
*
|
|
18
|
+
* An announcement is always a prefix, never a broadcast: a route claims that
|
|
19
|
+
* {@link prefix} and every path beneath it can be served. By convention a publisher
|
|
20
|
+
* announces each broadcast's exact path, so enumerating routes enumerates broadcasts;
|
|
21
|
+
* resolve one with the origin's `request(path)`. Narrow with a {@link Path.Pattern}
|
|
22
|
+
* locally to follow a subset.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export interface Update {
|
|
27
|
+
/**
|
|
28
|
+
* The prefix the route covers, relative to the origin (for a session, its URL path).
|
|
29
|
+
*/
|
|
30
|
+
prefix: Path.Valid;
|
|
31
|
+
/** What the filter's wildcards stood for, when this prefix pins all of them. */
|
|
32
|
+
captures: Path.Pattern[] | undefined;
|
|
33
|
+
/** Whether the prefix was announced, re-priced, or retracted. */
|
|
34
|
+
kind: Kind;
|
|
35
|
+
/** Hops and cost of the route; on a retraction, its last advertised values. */
|
|
36
|
+
route: Route;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Options for an announcement stream.
|
|
40
|
+
*
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
export interface Options {
|
|
44
|
+
/**
|
|
45
|
+
* Also report hidden routes: those with a path segment starting with `.` below the
|
|
46
|
+
* scope's literal head. Hidden routes are left out by default, so a platform can add
|
|
47
|
+
* `.`-named broadcasts (stats, internal routes) without them turning up in apps that
|
|
48
|
+
* list everything. Subscribing to a hidden path by name works either way.
|
|
49
|
+
*/
|
|
50
|
+
hidden?: boolean;
|
|
20
51
|
}
|
|
52
|
+
/** Whether a route covers the path after an update of this {@link Kind}. */
|
|
53
|
+
export declare function isActive(kind: Kind): boolean;
|
|
21
54
|
/**
|
|
22
55
|
* The write side of an announcement stream.
|
|
23
56
|
*
|
|
@@ -25,9 +58,6 @@ export interface Event {
|
|
|
25
58
|
*/
|
|
26
59
|
export declare class Producer {
|
|
27
60
|
#private;
|
|
28
|
-
/** Path prefix this stream is scoped to. */
|
|
29
|
-
prefix: Path.Valid;
|
|
30
|
-
constructor(prefix?: Path.Valid);
|
|
31
61
|
/**
|
|
32
62
|
* Settles once the stream closes: `null` on a clean close, or the abort {@link Error}.
|
|
33
63
|
* Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.
|
|
@@ -36,7 +66,7 @@ export declare class Producer {
|
|
|
36
66
|
/** A read handle for this announcement stream. */
|
|
37
67
|
consume(): Consumer;
|
|
38
68
|
/** Writes an announcement to the queue. */
|
|
39
|
-
append(
|
|
69
|
+
append(update: Update): void;
|
|
40
70
|
/** Closes the writer. Idempotent. */
|
|
41
71
|
close(abort?: Error): void;
|
|
42
72
|
}
|
|
@@ -44,92 +74,20 @@ export declare class Producer {
|
|
|
44
74
|
* The read side of an announcement stream.
|
|
45
75
|
*
|
|
46
76
|
* Created internally: obtain one from {@link Producer.consume} or the connection's
|
|
47
|
-
* `announced(
|
|
77
|
+
* `announced(scope)`.
|
|
48
78
|
*
|
|
49
79
|
* @public
|
|
50
80
|
*/
|
|
51
81
|
export declare class Consumer {
|
|
52
82
|
#private;
|
|
53
|
-
/** Path prefix this stream is scoped to. */
|
|
54
|
-
prefix: Path.Valid;
|
|
55
83
|
private constructor();
|
|
56
84
|
/** Settles once the stream closes; see {@link Producer.closed}. */
|
|
57
85
|
get closed(): GetPromise<Error | null>;
|
|
86
|
+
/** The announcements as they arrive, until the stream closes. */
|
|
87
|
+
[Symbol.asyncIterator](): AsyncGenerator<Update, void, undefined>;
|
|
58
88
|
/** Returns the next announcement. */
|
|
59
|
-
next(): Promise<
|
|
89
|
+
next(): Promise<Update | undefined>;
|
|
60
90
|
/** Closes the reader. Idempotent. */
|
|
61
91
|
close(abort?: Error): void;
|
|
62
92
|
}
|
|
63
|
-
/**
|
|
64
|
-
* What to watch, for {@link Broadcast}.
|
|
65
|
-
*
|
|
66
|
-
* @public
|
|
67
|
-
*/
|
|
68
|
-
export interface BroadcastProps {
|
|
69
|
-
/**
|
|
70
|
-
* The connection to watch on. Accepts a live {@link Established} session, or a reactive one
|
|
71
|
-
* (a `Connection.Reload`'s `established`), which is how the handle survives reconnects.
|
|
72
|
-
*/
|
|
73
|
-
connection: GetterInit<Established | undefined>;
|
|
74
|
-
/** The broadcast path to watch. */
|
|
75
|
-
path: Path.Valid;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* A reactive handle to a single broadcast: {@link Broadcast.active} holds a live
|
|
79
|
-
* {@link broadcast.Consumer} while the path is announced and `undefined` while nobody
|
|
80
|
-
* publishes it.
|
|
81
|
-
*
|
|
82
|
-
* Use this instead of {@link Established.consume} whenever the broadcast may not exist yet.
|
|
83
|
-
* Subscribing to a path nobody publishes gets the stream reset, so a consumer that races the
|
|
84
|
-
* publisher stays silent forever unless it retries; this waits for the announcement instead.
|
|
85
|
-
*
|
|
86
|
-
* A same-name republish re-consumes, so the handle attaches to the new instance rather than
|
|
87
|
-
* clinging to the dead one. A relay failover that keeps the same publisher does *not*: the
|
|
88
|
-
* subscription resumes across the new route, so `active` holds the same consumer throughout and
|
|
89
|
-
* never goes offline. Only a change of publisher produces an offline/online transition.
|
|
90
|
-
*
|
|
91
|
-
* Built from a reconnecting `Connection.Reload`, the handle also spans reconnects: the broadcast
|
|
92
|
-
* drops to `undefined` while disconnected and resolves again once the new connection announces it.
|
|
93
|
-
*
|
|
94
|
-
* Falls back to consuming blind (and warns once) on a relay without
|
|
95
|
-
* {@link Established.discovery}, where there is no announcement to wait for. `active` then
|
|
96
|
-
* means *assumed present* rather than known live: nothing reports whether the path exists, so
|
|
97
|
-
* a subscribe to a missing broadcast is how a caller finds out. The handle stays usable either
|
|
98
|
-
* way, and because it is scoped to the path rather than to one publisher, a subscribe made
|
|
99
|
-
* after a publisher finally appears succeeds.
|
|
100
|
-
*
|
|
101
|
-
* If discovery fails on a live session (the announcement stream is reset, or the relay
|
|
102
|
-
* refuses it) the handle goes offline and stays there: nothing reopens the stream on that
|
|
103
|
-
* connection. Build it from a `Connection.Reload` if you need it to recover, since a new
|
|
104
|
-
* connection starts a new stream.
|
|
105
|
-
*
|
|
106
|
-
* Close it to release the announcement stream and the current broadcast.
|
|
107
|
-
*
|
|
108
|
-
* @public
|
|
109
|
-
*/
|
|
110
|
-
export declare class Broadcast {
|
|
111
|
-
#private;
|
|
112
|
-
/** The broadcast path this handle watches. */
|
|
113
|
-
readonly path: Path.Valid;
|
|
114
|
-
/**
|
|
115
|
-
* The live broadcast, or `undefined` while it is offline.
|
|
116
|
-
*
|
|
117
|
-
* Borrowed, not yours to close: this handle owns the consumer and swaps it when the path is
|
|
118
|
-
* republished. `active` keeps pointing at whatever you closed, so once you drop the last
|
|
119
|
-
* reference the shared broadcast is gone and reads fail until the next announcement replaces
|
|
120
|
-
* it. Take a {@link broadcast.Consumer.clone} for a lifetime of your own, or close this whole
|
|
121
|
-
* handle to release everything.
|
|
122
|
-
*/
|
|
123
|
-
readonly active: Getter<broadcast.Consumer | undefined>;
|
|
124
|
-
/**
|
|
125
|
-
* Watch a path on a connection.
|
|
126
|
-
*
|
|
127
|
-
* Prefer `announcedBroadcast(path)` on the connection itself. Reach for this when the
|
|
128
|
-
* session you want to follow isn't either connection type, e.g. your own
|
|
129
|
-
* `Getter<Established | undefined>`.
|
|
130
|
-
*/
|
|
131
|
-
constructor({ connection, path }: BroadcastProps);
|
|
132
|
-
/** Closes the handle and the broadcast it currently holds. Idempotent. */
|
|
133
|
-
close(): void;
|
|
134
|
-
}
|
|
135
93
|
//# sourceMappingURL=announced.d.ts.map
|
package/announced.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"announced.d.ts","sourceRoot":"","sources":["../src/announced.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"announced.d.ts","sourceRoot":"","sources":["../src/announced.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AAEvC;;;;GAIG;AACH,MAAM,MAAM,IAAI,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,MAAM;IACtB;;OAEG;IACH,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC;IACnB,gFAAgF;IAChF,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,SAAS,CAAC;IACrC,iEAAiE;IACjE,IAAI,EAAE,IAAI,CAAC;IACX,+EAA+E;IAC/E,KAAK,EAAE,KAAK,CAAC;CACb;AAED;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACvB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAE5C;AAiBD;;;;GAIG;AACH,qBAAa,QAAQ;;IAGpB;;;OAGG;IACH,IAAI,MAAM,IAAI,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAErC;IAED,kDAAkD;IAClD,OAAO,IAAI,QAAQ,CAElB;IAED,2CAA2C;IAC3C,MAAM,CAAC,MAAM,EAAE,MAAM,QAKpB;IAED,qCAAqC;IACrC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,QAElB;CACD;AAMD;;;;;;;GAOG;AACH,qBAAa,QAAQ;;IAGpB,OAAO,eAEN;IAED,mEAAmE;IACnE,IAAI,MAAM,IAAI,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAErC;IAMD,iEAAiE;IAC1D,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,CAMvE;IAED,qCAAqC;IAC/B,IAAI,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAWxC;IAED,qCAAqC;IACrC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,QAElB;CACD"}
|
package/announced.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/* @ts-self-types="./announced.d.ts" */
|
|
2
2
|
/**
|
|
3
|
-
* Broadcast announcement streams: which broadcast paths are available under a
|
|
3
|
+
* Broadcast announcement streams: which broadcast paths are available under a scope.
|
|
4
4
|
*
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import { Once, Signal } from "@norskvideo/moq-signals";
|
|
8
|
+
/** Whether a route covers the path after an update of this {@link Kind}. */
|
|
9
|
+
export function isActive(kind) {
|
|
10
|
+
return kind !== "retracted";
|
|
11
|
+
}
|
|
9
12
|
/** Reactive backing state shared by announcement producers and consumers. */
|
|
10
13
|
class AnnounceState {
|
|
11
14
|
queue = new Signal([]);
|
|
@@ -26,12 +29,7 @@ function closeState(state, abort) {
|
|
|
26
29
|
* @public
|
|
27
30
|
*/
|
|
28
31
|
export class Producer {
|
|
29
|
-
/** Path prefix this stream is scoped to. */
|
|
30
|
-
prefix;
|
|
31
32
|
#state = new AnnounceState();
|
|
32
|
-
constructor(prefix = Path.empty()) {
|
|
33
|
-
this.prefix = prefix;
|
|
34
|
-
}
|
|
35
33
|
/**
|
|
36
34
|
* Settles once the stream closes: `null` on a clean close, or the abort {@link Error}.
|
|
37
35
|
* Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.
|
|
@@ -41,14 +39,14 @@ export class Producer {
|
|
|
41
39
|
}
|
|
42
40
|
/** A read handle for this announcement stream. */
|
|
43
41
|
consume() {
|
|
44
|
-
return makeConsumer(this
|
|
42
|
+
return makeConsumer(this.#state);
|
|
45
43
|
}
|
|
46
44
|
/** Writes an announcement to the queue. */
|
|
47
|
-
append(
|
|
45
|
+
append(update) {
|
|
48
46
|
if (this.#state.closed.peek() !== undefined)
|
|
49
47
|
throw new Error("announcements are closed");
|
|
50
48
|
this.#state.queue.mutate((queue) => {
|
|
51
|
-
queue.push(
|
|
49
|
+
queue.push(update);
|
|
52
50
|
});
|
|
53
51
|
}
|
|
54
52
|
/** Closes the writer. Idempotent. */
|
|
@@ -63,16 +61,13 @@ let makeConsumer;
|
|
|
63
61
|
* The read side of an announcement stream.
|
|
64
62
|
*
|
|
65
63
|
* Created internally: obtain one from {@link Producer.consume} or the connection's
|
|
66
|
-
* `announced(
|
|
64
|
+
* `announced(scope)`.
|
|
67
65
|
*
|
|
68
66
|
* @public
|
|
69
67
|
*/
|
|
70
68
|
export class Consumer {
|
|
71
|
-
/** Path prefix this stream is scoped to. */
|
|
72
|
-
prefix;
|
|
73
69
|
#state;
|
|
74
|
-
constructor(
|
|
75
|
-
this.prefix = prefix;
|
|
70
|
+
constructor(state) {
|
|
76
71
|
this.#state = state;
|
|
77
72
|
}
|
|
78
73
|
/** Settles once the stream closes; see {@link Producer.closed}. */
|
|
@@ -80,7 +75,16 @@ export class Consumer {
|
|
|
80
75
|
return this.#state.closed;
|
|
81
76
|
}
|
|
82
77
|
static {
|
|
83
|
-
makeConsumer = (
|
|
78
|
+
makeConsumer = (state) => new Consumer(state);
|
|
79
|
+
}
|
|
80
|
+
/** The announcements as they arrive, until the stream closes. */
|
|
81
|
+
async *[Symbol.asyncIterator]() {
|
|
82
|
+
for (;;) {
|
|
83
|
+
const update = await this.next();
|
|
84
|
+
if (!update)
|
|
85
|
+
return;
|
|
86
|
+
yield update;
|
|
87
|
+
}
|
|
84
88
|
}
|
|
85
89
|
/** Returns the next announcement. */
|
|
86
90
|
async next() {
|
|
@@ -101,143 +105,4 @@ export class Consumer {
|
|
|
101
105
|
closeState(this.#state, abort);
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
|
-
// Connections already warned about missing broadcast discovery, so the fallback logs at most
|
|
105
|
-
// once per connection instead of once per watched path.
|
|
106
|
-
const warnedNoDiscovery = new WeakSet();
|
|
107
|
-
/**
|
|
108
|
-
* A reactive handle to a single broadcast: {@link Broadcast.active} holds a live
|
|
109
|
-
* {@link broadcast.Consumer} while the path is announced and `undefined` while nobody
|
|
110
|
-
* publishes it.
|
|
111
|
-
*
|
|
112
|
-
* Use this instead of {@link Established.consume} whenever the broadcast may not exist yet.
|
|
113
|
-
* Subscribing to a path nobody publishes gets the stream reset, so a consumer that races the
|
|
114
|
-
* publisher stays silent forever unless it retries; this waits for the announcement instead.
|
|
115
|
-
*
|
|
116
|
-
* A same-name republish re-consumes, so the handle attaches to the new instance rather than
|
|
117
|
-
* clinging to the dead one. A relay failover that keeps the same publisher does *not*: the
|
|
118
|
-
* subscription resumes across the new route, so `active` holds the same consumer throughout and
|
|
119
|
-
* never goes offline. Only a change of publisher produces an offline/online transition.
|
|
120
|
-
*
|
|
121
|
-
* Built from a reconnecting `Connection.Reload`, the handle also spans reconnects: the broadcast
|
|
122
|
-
* drops to `undefined` while disconnected and resolves again once the new connection announces it.
|
|
123
|
-
*
|
|
124
|
-
* Falls back to consuming blind (and warns once) on a relay without
|
|
125
|
-
* {@link Established.discovery}, where there is no announcement to wait for. `active` then
|
|
126
|
-
* means *assumed present* rather than known live: nothing reports whether the path exists, so
|
|
127
|
-
* a subscribe to a missing broadcast is how a caller finds out. The handle stays usable either
|
|
128
|
-
* way, and because it is scoped to the path rather than to one publisher, a subscribe made
|
|
129
|
-
* after a publisher finally appears succeeds.
|
|
130
|
-
*
|
|
131
|
-
* If discovery fails on a live session (the announcement stream is reset, or the relay
|
|
132
|
-
* refuses it) the handle goes offline and stays there: nothing reopens the stream on that
|
|
133
|
-
* connection. Build it from a `Connection.Reload` if you need it to recover, since a new
|
|
134
|
-
* connection starts a new stream.
|
|
135
|
-
*
|
|
136
|
-
* Close it to release the announcement stream and the current broadcast.
|
|
137
|
-
*
|
|
138
|
-
* @public
|
|
139
|
-
*/
|
|
140
|
-
export class Broadcast {
|
|
141
|
-
/** The broadcast path this handle watches. */
|
|
142
|
-
path;
|
|
143
|
-
/**
|
|
144
|
-
* The live broadcast, or `undefined` while it is offline.
|
|
145
|
-
*
|
|
146
|
-
* Borrowed, not yours to close: this handle owns the consumer and swaps it when the path is
|
|
147
|
-
* republished. `active` keeps pointing at whatever you closed, so once you drop the last
|
|
148
|
-
* reference the shared broadcast is gone and reads fail until the next announcement replaces
|
|
149
|
-
* it. Take a {@link broadcast.Consumer.clone} for a lifetime of your own, or close this whole
|
|
150
|
-
* handle to release everything.
|
|
151
|
-
*/
|
|
152
|
-
active;
|
|
153
|
-
#active = new Signal(undefined);
|
|
154
|
-
#signals = new Effect();
|
|
155
|
-
/**
|
|
156
|
-
* Watch a path on a connection.
|
|
157
|
-
*
|
|
158
|
-
* Prefer `announcedBroadcast(path)` on the connection itself. Reach for this when the
|
|
159
|
-
* session you want to follow isn't either connection type, e.g. your own
|
|
160
|
-
* `Getter<Established | undefined>`.
|
|
161
|
-
*/
|
|
162
|
-
constructor({ connection, path }) {
|
|
163
|
-
this.path = path;
|
|
164
|
-
this.active = this.#active;
|
|
165
|
-
const source = getter(connection);
|
|
166
|
-
this.#signals.run((effect) => {
|
|
167
|
-
const conn = effect.get(source);
|
|
168
|
-
if (!conn)
|
|
169
|
-
return;
|
|
170
|
-
// Without discovery no announcement ever arrives, so waiting would hang forever.
|
|
171
|
-
if (!conn.discovery) {
|
|
172
|
-
if (!warnedNoDiscovery.has(conn)) {
|
|
173
|
-
warnedNoDiscovery.add(conn);
|
|
174
|
-
console.warn("relay does not support broadcast discovery; consuming without waiting.");
|
|
175
|
-
}
|
|
176
|
-
const blind = conn.consume(path);
|
|
177
|
-
effect.cleanup(() => blind.close());
|
|
178
|
-
effect.set(this.#active, blind, undefined);
|
|
179
|
-
// The announcement-gated path below goes offline when the stream ends with the
|
|
180
|
-
// session; without discovery there is no stream, so watch the session itself.
|
|
181
|
-
// A consumed broadcast is a path-scoped handle, not a subscription, so its own
|
|
182
|
-
// `closed` says nothing about whether the path exists or the session is alive.
|
|
183
|
-
// Raced against the run's teardown so a closed handle isn't retained until the
|
|
184
|
-
// session ends; the cleanup above has already cleared `active` in that case.
|
|
185
|
-
effect.spawn(async () => {
|
|
186
|
-
await Promise.race([effect.cancel, conn.closed]);
|
|
187
|
-
if (this.#active.peek() === blind)
|
|
188
|
-
this.#active.set(undefined);
|
|
189
|
-
});
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const announced = conn.announced(path);
|
|
193
|
-
effect.cleanup(() => announced.close());
|
|
194
|
-
let current;
|
|
195
|
-
const offline = () => {
|
|
196
|
-
const mine = current;
|
|
197
|
-
current?.close();
|
|
198
|
-
current = undefined;
|
|
199
|
-
// Only clear what this run put there. A spawn task that resumes after its run was
|
|
200
|
-
// torn down would otherwise wipe the consumer a newer run already installed.
|
|
201
|
-
if (this.#active.peek() === mine)
|
|
202
|
-
this.#active.set(undefined);
|
|
203
|
-
};
|
|
204
|
-
effect.cleanup(offline);
|
|
205
|
-
effect.spawn(async () => {
|
|
206
|
-
try {
|
|
207
|
-
for (;;) {
|
|
208
|
-
const event = await Promise.race([effect.cancel, announced.next()]);
|
|
209
|
-
if (!event)
|
|
210
|
-
break;
|
|
211
|
-
// Scoped to `path`, so the exact broadcast arrives with an empty suffix; ignore children.
|
|
212
|
-
if (event.path !== Path.empty())
|
|
213
|
-
continue;
|
|
214
|
-
if (event.active) {
|
|
215
|
-
// A live subscription survives a redundant (re-)announce; only replace a dead one.
|
|
216
|
-
if (current && current.closed.peek() === undefined)
|
|
217
|
-
continue;
|
|
218
|
-
current?.close();
|
|
219
|
-
current = conn.consume(path);
|
|
220
|
-
this.#active.set(current);
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
offline();
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
catch (err) {
|
|
228
|
-
// Discovery failed: the session died under the stream, or the relay refused
|
|
229
|
-
// to answer. Nothing reopens it on this connection, so say so out loud.
|
|
230
|
-
console.warn("broadcast discovery failed", err);
|
|
231
|
-
}
|
|
232
|
-
// The stream ended, or this run was torn down (its cleanup already ran). Either
|
|
233
|
-
// way there is nothing left announcing the path, so don't hold a dead broadcast.
|
|
234
|
-
offline();
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
/** Closes the handle and the broadcast it currently holds. Idempotent. */
|
|
239
|
-
close() {
|
|
240
|
-
this.#signals.close();
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
108
|
//# sourceMappingURL=announced.js.map
|
package/announced.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"announced.js","sourceRoot":"","sources":["../src/announced.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,MAAM,EAAiD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3G,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAclC,6EAA6E;AAC7E,MAAM,aAAa;IAClB,KAAK,GAAG,IAAI,MAAM,CAAU,EAAE,CAAC,CAAC;IAChC,MAAM,GAAG,IAAI,IAAI,EAAgB,CAAC;CAClC;AAED,yFAAyF;AACzF,SAAS,UAAU,CAAC,KAAoB,EAAE,KAAa;IACtD,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO;IAC9C,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;IAChC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5B,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,QAAQ;IACpB,4CAA4C;IAC5C,MAAM,CAAa;IAEnB,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;IAE7B,YAAY,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,CAAC;IAED;;;OAGG;IACH,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,kDAAkD;IAClD,OAAO;QACN,OAAO,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IAED,2CAA2C;IAC3C,MAAM,CAAC,KAAY;QAClB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACzF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YAClC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,qCAAqC;IACrC,KAAK,CAAC,KAAa;QAClB,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;CACD;AAED,sFAAsF;AACtF,sFAAsF;AACtF,IAAI,YAAoE,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,OAAO,QAAQ;IACpB,4CAA4C;IAC5C,MAAM,CAAa;IAEnB,MAAM,CAAgB;IAEtB,YAAoB,MAAkB,EAAE,KAAoB;QAC3D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,mEAAmE;IACnE,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED;QACC,YAAY,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC/D,CAAC;IAED,qCAAqC;IACrC,KAAK,CAAC,IAAI;QACT,SAAS,CAAC;YACT,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YAClD,IAAI,QAAQ;gBAAE,OAAO,QAAQ,CAAC;YAE9B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACzC,IAAI,MAAM,YAAY,KAAK;gBAAE,MAAM,MAAM,CAAC;YAC1C,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YAE3C,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC1D,CAAC;IACF,CAAC;IAED,qCAAqC;IACrC,KAAK,CAAC,KAAa;QAClB,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;CACD;AAED,6FAA6F;AAC7F,wDAAwD;AACxD,MAAM,iBAAiB,GAAG,IAAI,OAAO,EAAe,CAAC;AAkBrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,OAAO,SAAS;IACrB,8CAA8C;IACrC,IAAI,CAAa;IAE1B;;;;;;;;OAQG;IACM,MAAM,CAAyC;IAExD,OAAO,GAAG,IAAI,MAAM,CAAiC,SAAS,CAAC,CAAC;IAChE,QAAQ,GAAG,IAAI,MAAM,EAAE,CAAC;IAExB;;;;;;OAMG;IACH,YAAY,EAAE,UAAU,EAAE,IAAI,EAAkB;QAC/C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;QAE3B,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAClC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAChC,IAAI,CAAC,IAAI;gBAAE,OAAO;YAElB,iFAAiF;YACjF,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACrB,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBAClC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAC5B,OAAO,CAAC,IAAI,CAAC,wEAAwE,CAAC,CAAC;gBACxF,CAAC;gBAED,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBACjC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;gBACpC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;gBAE3C,+EAA+E;gBAC/E,8EAA8E;gBAC9E,+EAA+E;gBAC/E,+EAA+E;gBAC/E,+EAA+E;gBAC/E,6EAA6E;gBAC7E,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE;oBACvB,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;oBACjD,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,KAAK;wBAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAChE,CAAC,CAAC,CAAC;gBACH,OAAO;YACR,CAAC;YAED,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;YAExC,IAAI,OAAuC,CAAC;YAC5C,MAAM,OAAO,GAAG,GAAG,EAAE;gBACpB,MAAM,IAAI,GAAG,OAAO,CAAC;gBACrB,OAAO,EAAE,KAAK,EAAE,CAAC;gBACjB,OAAO,GAAG,SAAS,CAAC;gBACpB,kFAAkF;gBAClF,6EAA6E;gBAC7E,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,IAAI;oBAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC/D,CAAC,CAAC;YACF,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAExB,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE;gBACvB,IAAI,CAAC;oBACJ,SAAS,CAAC;wBACT,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;wBACpE,IAAI,CAAC,KAAK;4BAAE,MAAM;wBAElB,0FAA0F;wBAC1F,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,EAAE;4BAAE,SAAS;wBAE1C,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;4BAClB,mFAAmF;4BACnF,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,SAAS;gCAAE,SAAS;4BAC7D,OAAO,EAAE,KAAK,EAAE,CAAC;4BACjB,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;4BAC7B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;wBAC3B,CAAC;6BAAM,CAAC;4BACP,OAAO,EAAE,CAAC;wBACX,CAAC;oBACF,CAAC;gBACF,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,4EAA4E;oBAC5E,wEAAwE;oBACxE,OAAO,CAAC,IAAI,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;gBACjD,CAAC;gBAED,gFAAgF;gBAChF,iFAAiF;gBACjF,OAAO,EAAE,CAAC;YACX,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,KAAK;QACJ,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;CACD","sourcesContent":["/**\n * Broadcast announcement streams: which broadcast paths are available under a prefix.\n *\n * @module\n */\nimport { Effect, type GetPromise, type Getter, type GetterInit, getter, Once, Signal } from \"@moq/signals\";\nimport type * as broadcast from \"./broadcast.js\";\nimport type { Established } from \"./connection/established.js\";\nimport * as Path from \"./path.js\";\n\n/**\n * The availability of a broadcast.\n *\n * @public\n */\nexport interface Event {\n\t/** Broadcast path relative to the prefix passed to `announced()`. */\n\tpath: Path.Valid;\n\t/** True when the broadcast is available, false when it was removed. */\n\tactive: boolean;\n}\n\n/** Reactive backing state shared by announcement producers and consumers. */\nclass AnnounceState {\n\tqueue = new Signal<Event[]>([]);\n\tclosed = new Once<Error | null>();\n}\n\n// Once.set throws on a second settle, and both ends of a stream can close independently.\nfunction closeState(state: AnnounceState, abort?: Error) {\n\tif (state.closed.peek() !== undefined) return;\n\tstate.closed.set(abort ?? null);\n\tstate.queue.mutate((queue) => {\n\t\tqueue.length = 0;\n\t});\n}\n\n/**\n * The write side of an announcement stream.\n *\n * @public\n */\nexport class Producer {\n\t/** Path prefix this stream is scoped to. */\n\tprefix: Path.Valid;\n\n\t#state = new AnnounceState();\n\n\tconstructor(prefix = Path.empty()) {\n\t\tthis.prefix = prefix;\n\t}\n\n\t/**\n\t * Settles once the stream closes: `null` on a clean close, or the abort {@link Error}.\n\t * Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.\n\t */\n\tget closed(): GetPromise<Error | null> {\n\t\treturn this.#state.closed;\n\t}\n\n\t/** A read handle for this announcement stream. */\n\tconsume(): Consumer {\n\t\treturn makeConsumer(this.prefix, this.#state);\n\t}\n\n\t/** Writes an announcement to the queue. */\n\tappend(event: Event) {\n\t\tif (this.#state.closed.peek() !== undefined) throw new Error(\"announcements are closed\");\n\t\tthis.#state.queue.mutate((queue) => {\n\t\t\tqueue.push(event);\n\t\t});\n\t}\n\n\t/** Closes the writer. Idempotent. */\n\tclose(abort?: Error) {\n\t\tcloseState(this.#state, abort);\n\t}\n}\n\n// Constructs a Consumer from within this module without exposing a public constructor\n// that would leak the unexported AnnounceState. Assigned in the class's static block.\nlet makeConsumer: (prefix: Path.Valid, state: AnnounceState) => Consumer;\n\n/**\n * The read side of an announcement stream.\n *\n * Created internally: obtain one from {@link Producer.consume} or the connection's\n * `announced(prefix)`.\n *\n * @public\n */\nexport class Consumer {\n\t/** Path prefix this stream is scoped to. */\n\tprefix: Path.Valid;\n\n\t#state: AnnounceState;\n\n\tprivate constructor(prefix: Path.Valid, state: AnnounceState) {\n\t\tthis.prefix = prefix;\n\t\tthis.#state = state;\n\t}\n\n\t/** Settles once the stream closes; see {@link Producer.closed}. */\n\tget closed(): GetPromise<Error | null> {\n\t\treturn this.#state.closed;\n\t}\n\n\tstatic {\n\t\tmakeConsumer = (prefix, state) => new Consumer(prefix, state);\n\t}\n\n\t/** Returns the next announcement. */\n\tasync next(): Promise<Event | undefined> {\n\t\tfor (;;) {\n\t\t\tconst announce = this.#state.queue.peek().shift();\n\t\t\tif (announce) return announce;\n\n\t\t\tconst closed = this.#state.closed.peek();\n\t\t\tif (closed instanceof Error) throw closed;\n\t\t\tif (closed !== undefined) return undefined;\n\n\t\t\tawait Signal.race(this.#state.queue, this.#state.closed);\n\t\t}\n\t}\n\n\t/** Closes the reader. Idempotent. */\n\tclose(abort?: Error) {\n\t\tcloseState(this.#state, abort);\n\t}\n}\n\n// Connections already warned about missing broadcast discovery, so the fallback logs at most\n// once per connection instead of once per watched path.\nconst warnedNoDiscovery = new WeakSet<Established>();\n\n/**\n * What to watch, for {@link Broadcast}.\n *\n * @public\n */\nexport interface BroadcastProps {\n\t/**\n\t * The connection to watch on. Accepts a live {@link Established} session, or a reactive one\n\t * (a `Connection.Reload`'s `established`), which is how the handle survives reconnects.\n\t */\n\tconnection: GetterInit<Established | undefined>;\n\n\t/** The broadcast path to watch. */\n\tpath: Path.Valid;\n}\n\n/**\n * A reactive handle to a single broadcast: {@link Broadcast.active} holds a live\n * {@link broadcast.Consumer} while the path is announced and `undefined` while nobody\n * publishes it.\n *\n * Use this instead of {@link Established.consume} whenever the broadcast may not exist yet.\n * Subscribing to a path nobody publishes gets the stream reset, so a consumer that races the\n * publisher stays silent forever unless it retries; this waits for the announcement instead.\n *\n * A same-name republish re-consumes, so the handle attaches to the new instance rather than\n * clinging to the dead one. A relay failover that keeps the same publisher does *not*: the\n * subscription resumes across the new route, so `active` holds the same consumer throughout and\n * never goes offline. Only a change of publisher produces an offline/online transition.\n *\n * Built from a reconnecting `Connection.Reload`, the handle also spans reconnects: the broadcast\n * drops to `undefined` while disconnected and resolves again once the new connection announces it.\n *\n * Falls back to consuming blind (and warns once) on a relay without\n * {@link Established.discovery}, where there is no announcement to wait for. `active` then\n * means *assumed present* rather than known live: nothing reports whether the path exists, so\n * a subscribe to a missing broadcast is how a caller finds out. The handle stays usable either\n * way, and because it is scoped to the path rather than to one publisher, a subscribe made\n * after a publisher finally appears succeeds.\n *\n * If discovery fails on a live session (the announcement stream is reset, or the relay\n * refuses it) the handle goes offline and stays there: nothing reopens the stream on that\n * connection. Build it from a `Connection.Reload` if you need it to recover, since a new\n * connection starts a new stream.\n *\n * Close it to release the announcement stream and the current broadcast.\n *\n * @public\n */\nexport class Broadcast {\n\t/** The broadcast path this handle watches. */\n\treadonly path: Path.Valid;\n\n\t/**\n\t * The live broadcast, or `undefined` while it is offline.\n\t *\n\t * Borrowed, not yours to close: this handle owns the consumer and swaps it when the path is\n\t * republished. `active` keeps pointing at whatever you closed, so once you drop the last\n\t * reference the shared broadcast is gone and reads fail until the next announcement replaces\n\t * it. Take a {@link broadcast.Consumer.clone} for a lifetime of your own, or close this whole\n\t * handle to release everything.\n\t */\n\treadonly active: Getter<broadcast.Consumer | undefined>;\n\n\t#active = new Signal<broadcast.Consumer | undefined>(undefined);\n\t#signals = new Effect();\n\n\t/**\n\t * Watch a path on a connection.\n\t *\n\t * Prefer `announcedBroadcast(path)` on the connection itself. Reach for this when the\n\t * session you want to follow isn't either connection type, e.g. your own\n\t * `Getter<Established | undefined>`.\n\t */\n\tconstructor({ connection, path }: BroadcastProps) {\n\t\tthis.path = path;\n\t\tthis.active = this.#active;\n\n\t\tconst source = getter(connection);\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst conn = effect.get(source);\n\t\t\tif (!conn) return;\n\n\t\t\t// Without discovery no announcement ever arrives, so waiting would hang forever.\n\t\t\tif (!conn.discovery) {\n\t\t\t\tif (!warnedNoDiscovery.has(conn)) {\n\t\t\t\t\twarnedNoDiscovery.add(conn);\n\t\t\t\t\tconsole.warn(\"relay does not support broadcast discovery; consuming without waiting.\");\n\t\t\t\t}\n\n\t\t\t\tconst blind = conn.consume(path);\n\t\t\t\teffect.cleanup(() => blind.close());\n\t\t\t\teffect.set(this.#active, blind, undefined);\n\n\t\t\t\t// The announcement-gated path below goes offline when the stream ends with the\n\t\t\t\t// session; without discovery there is no stream, so watch the session itself.\n\t\t\t\t// A consumed broadcast is a path-scoped handle, not a subscription, so its own\n\t\t\t\t// `closed` says nothing about whether the path exists or the session is alive.\n\t\t\t\t// Raced against the run's teardown so a closed handle isn't retained until the\n\t\t\t\t// session ends; the cleanup above has already cleared `active` in that case.\n\t\t\t\teffect.spawn(async () => {\n\t\t\t\t\tawait Promise.race([effect.cancel, conn.closed]);\n\t\t\t\t\tif (this.#active.peek() === blind) this.#active.set(undefined);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst announced = conn.announced(path);\n\t\t\teffect.cleanup(() => announced.close());\n\n\t\t\tlet current: broadcast.Consumer | undefined;\n\t\t\tconst offline = () => {\n\t\t\t\tconst mine = current;\n\t\t\t\tcurrent?.close();\n\t\t\t\tcurrent = undefined;\n\t\t\t\t// Only clear what this run put there. A spawn task that resumes after its run was\n\t\t\t\t// torn down would otherwise wipe the consumer a newer run already installed.\n\t\t\t\tif (this.#active.peek() === mine) this.#active.set(undefined);\n\t\t\t};\n\t\t\teffect.cleanup(offline);\n\n\t\t\teffect.spawn(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tfor (;;) {\n\t\t\t\t\t\tconst event = await Promise.race([effect.cancel, announced.next()]);\n\t\t\t\t\t\tif (!event) break;\n\n\t\t\t\t\t\t// Scoped to `path`, so the exact broadcast arrives with an empty suffix; ignore children.\n\t\t\t\t\t\tif (event.path !== Path.empty()) continue;\n\n\t\t\t\t\t\tif (event.active) {\n\t\t\t\t\t\t\t// A live subscription survives a redundant (re-)announce; only replace a dead one.\n\t\t\t\t\t\t\tif (current && current.closed.peek() === undefined) continue;\n\t\t\t\t\t\t\tcurrent?.close();\n\t\t\t\t\t\t\tcurrent = conn.consume(path);\n\t\t\t\t\t\t\tthis.#active.set(current);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\toffline();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Discovery failed: the session died under the stream, or the relay refused\n\t\t\t\t\t// to answer. Nothing reopens it on this connection, so say so out loud.\n\t\t\t\t\tconsole.warn(\"broadcast discovery failed\", err);\n\t\t\t\t}\n\n\t\t\t\t// The stream ended, or this run was torn down (its cleanup already ran). Either\n\t\t\t\t// way there is nothing left announcing the path, so don't hold a dead broadcast.\n\t\t\t\toffline();\n\t\t\t});\n\t\t});\n\t}\n\n\t/** Closes the handle and the broadcast it currently holds. Idempotent. */\n\tclose() {\n\t\tthis.#signals.close();\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"announced.js","sourceRoot":"","sources":["../src/announced.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Broadcast announcement streams: which broadcast paths are available under a scope.\n *\n * @module\n */\nimport { type GetPromise, Once, Signal } from \"@moq/signals\";\nimport type { Route } from \"./hop.js\";\nimport type * as Path from \"./path.js\";\n\n/**\n * What an {@link Update} reports about its prefix.\n *\n * @public\n */\nexport type Kind = \"announced\" | \"updated\" | \"retracted\";\n\n/**\n * A route announcement, update, or retraction.\n *\n * An announcement is always a prefix, never a broadcast: a route claims that\n * {@link prefix} and every path beneath it can be served. By convention a publisher\n * announces each broadcast's exact path, so enumerating routes enumerates broadcasts;\n * resolve one with the origin's `request(path)`. Narrow with a {@link Path.Pattern}\n * locally to follow a subset.\n *\n * @public\n */\nexport interface Update {\n\t/**\n\t * The prefix the route covers, relative to the origin (for a session, its URL path).\n\t */\n\tprefix: Path.Valid;\n\t/** What the filter's wildcards stood for, when this prefix pins all of them. */\n\tcaptures: Path.Pattern[] | undefined;\n\t/** Whether the prefix was announced, re-priced, or retracted. */\n\tkind: Kind;\n\t/** Hops and cost of the route; on a retraction, its last advertised values. */\n\troute: Route;\n}\n\n/**\n * Options for an announcement stream.\n *\n * @public\n */\nexport interface Options {\n\t/**\n\t * Also report hidden routes: those with a path segment starting with `.` below the\n\t * scope's literal head. Hidden routes are left out by default, so a platform can add\n\t * `.`-named broadcasts (stats, internal routes) without them turning up in apps that\n\t * list everything. Subscribing to a hidden path by name works either way.\n\t */\n\thidden?: boolean;\n}\n\n/** Whether a route covers the path after an update of this {@link Kind}. */\nexport function isActive(kind: Kind): boolean {\n\treturn kind !== \"retracted\";\n}\n\n/** Reactive backing state shared by announcement producers and consumers. */\nclass AnnounceState {\n\tqueue = new Signal<Update[]>([]);\n\tclosed = new Once<Error | null>();\n}\n\n// Once.set throws on a second settle, and both ends of a stream can close independently.\nfunction closeState(state: AnnounceState, abort?: Error) {\n\tif (state.closed.peek() !== undefined) return;\n\tstate.closed.set(abort ?? null);\n\tstate.queue.mutate((queue) => {\n\t\tqueue.length = 0;\n\t});\n}\n\n/**\n * The write side of an announcement stream.\n *\n * @public\n */\nexport class Producer {\n\t#state = new AnnounceState();\n\n\t/**\n\t * Settles once the stream closes: `null` on a clean close, or the abort {@link Error}.\n\t * Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.\n\t */\n\tget closed(): GetPromise<Error | null> {\n\t\treturn this.#state.closed;\n\t}\n\n\t/** A read handle for this announcement stream. */\n\tconsume(): Consumer {\n\t\treturn makeConsumer(this.#state);\n\t}\n\n\t/** Writes an announcement to the queue. */\n\tappend(update: Update) {\n\t\tif (this.#state.closed.peek() !== undefined) throw new Error(\"announcements are closed\");\n\t\tthis.#state.queue.mutate((queue) => {\n\t\t\tqueue.push(update);\n\t\t});\n\t}\n\n\t/** Closes the writer. Idempotent. */\n\tclose(abort?: Error) {\n\t\tcloseState(this.#state, abort);\n\t}\n}\n\n// Constructs a Consumer from within this module without exposing a public constructor\n// that would leak the unexported AnnounceState. Assigned in the class's static block.\nlet makeConsumer: (state: AnnounceState) => Consumer;\n\n/**\n * The read side of an announcement stream.\n *\n * Created internally: obtain one from {@link Producer.consume} or the connection's\n * `announced(scope)`.\n *\n * @public\n */\nexport class Consumer {\n\t#state: AnnounceState;\n\n\tprivate constructor(state: AnnounceState) {\n\t\tthis.#state = state;\n\t}\n\n\t/** Settles once the stream closes; see {@link Producer.closed}. */\n\tget closed(): GetPromise<Error | null> {\n\t\treturn this.#state.closed;\n\t}\n\n\tstatic {\n\t\tmakeConsumer = (state) => new Consumer(state);\n\t}\n\n\t/** The announcements as they arrive, until the stream closes. */\n\tasync *[Symbol.asyncIterator](): AsyncGenerator<Update, void, undefined> {\n\t\tfor (;;) {\n\t\t\tconst update = await this.next();\n\t\t\tif (!update) return;\n\t\t\tyield update;\n\t\t}\n\t}\n\n\t/** Returns the next announcement. */\n\tasync next(): Promise<Update | undefined> {\n\t\tfor (;;) {\n\t\t\tconst announce = this.#state.queue.peek().shift();\n\t\t\tif (announce) return announce;\n\n\t\t\tconst closed = this.#state.closed.peek();\n\t\t\tif (closed instanceof Error) throw closed;\n\t\t\tif (closed !== undefined) return undefined;\n\n\t\t\tawait Signal.race(this.#state.queue, this.#state.closed);\n\t\t}\n\t}\n\n\t/** Closes the reader. Idempotent. */\n\tclose(abort?: Error) {\n\t\tcloseState(this.#state, abort);\n\t}\n}\n"]}
|