@norskvideo/moq-net 0.1.2 → 0.1.4

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.
Files changed (310) hide show
  1. package/README.md +1 -0
  2. package/announced.d.ts +111 -21
  3. package/announced.d.ts.map +1 -1
  4. package/announced.js +213 -40
  5. package/announced.js.map +1 -1
  6. package/broadcast.d.ts +80 -25
  7. package/broadcast.d.ts.map +1 -1
  8. package/broadcast.js +270 -49
  9. package/broadcast.js.map +1 -1
  10. package/connection/accept.d.ts +6 -0
  11. package/connection/accept.d.ts.map +1 -1
  12. package/connection/accept.js +42 -13
  13. package/connection/accept.js.map +1 -1
  14. package/connection/browser.d.ts +5 -0
  15. package/connection/browser.d.ts.map +1 -0
  16. package/connection/browser.js +31 -0
  17. package/connection/browser.js.map +1 -0
  18. package/connection/connect.d.ts +43 -1
  19. package/connection/connect.d.ts.map +1 -1
  20. package/connection/connect.js +118 -123
  21. package/connection/connect.js.map +1 -1
  22. package/connection/established.d.ts +43 -16
  23. package/connection/established.d.ts.map +1 -1
  24. package/connection/established.js.map +1 -1
  25. package/connection/handshake.d.ts +12 -1
  26. package/connection/handshake.d.ts.map +1 -1
  27. package/connection/handshake.js +30 -14
  28. package/connection/handshake.js.map +1 -1
  29. package/connection/index.d.ts +8 -0
  30. package/connection/index.d.ts.map +1 -1
  31. package/connection/index.js +6 -0
  32. package/connection/index.js.map +1 -1
  33. package/connection/reload.d.ts +66 -9
  34. package/connection/reload.d.ts.map +1 -1
  35. package/connection/reload.js +192 -66
  36. package/connection/reload.js.map +1 -1
  37. package/connection/stats.d.ts +92 -0
  38. package/connection/stats.d.ts.map +1 -0
  39. package/connection/stats.js +35 -0
  40. package/connection/stats.js.map +1 -0
  41. package/connection/transport.d.ts +10 -0
  42. package/connection/transport.d.ts.map +1 -0
  43. package/connection/transport.js +12 -0
  44. package/connection/transport.js.map +1 -0
  45. package/consume.d.ts +44 -0
  46. package/consume.d.ts.map +1 -0
  47. package/consume.js +60 -0
  48. package/consume.js.map +1 -0
  49. package/datagram.d.ts +23 -0
  50. package/datagram.d.ts.map +1 -0
  51. package/datagram.js +3 -0
  52. package/datagram.js.map +1 -0
  53. package/error.d.ts +76 -0
  54. package/error.d.ts.map +1 -0
  55. package/error.js +107 -0
  56. package/error.js.map +1 -0
  57. package/group.d.ts +143 -38
  58. package/group.d.ts.map +1 -1
  59. package/group.js +270 -68
  60. package/group.js.map +1 -1
  61. package/ietf/adapter.d.ts +2 -2
  62. package/ietf/adapter.d.ts.map +1 -1
  63. package/ietf/adapter.js +20 -10
  64. package/ietf/adapter.js.map +1 -1
  65. package/ietf/aliases.d.ts +73 -5
  66. package/ietf/aliases.d.ts.map +1 -1
  67. package/ietf/aliases.js +121 -14
  68. package/ietf/aliases.js.map +1 -1
  69. package/ietf/cluster.d.ts +141 -0
  70. package/ietf/cluster.d.ts.map +1 -0
  71. package/ietf/cluster.js +204 -0
  72. package/ietf/cluster.js.map +1 -0
  73. package/ietf/connection.d.ts +39 -6
  74. package/ietf/connection.d.ts.map +1 -1
  75. package/ietf/connection.js +73 -12
  76. package/ietf/connection.js.map +1 -1
  77. package/ietf/fetch.d.ts +17 -0
  78. package/ietf/fetch.d.ts.map +1 -1
  79. package/ietf/fetch.js +21 -0
  80. package/ietf/fetch.js.map +1 -1
  81. package/ietf/filter.d.ts +87 -0
  82. package/ietf/filter.d.ts.map +1 -0
  83. package/ietf/filter.js +292 -0
  84. package/ietf/filter.js.map +1 -0
  85. package/ietf/index.d.ts +2 -0
  86. package/ietf/index.d.ts.map +1 -1
  87. package/ietf/index.js +3 -0
  88. package/ietf/index.js.map +1 -1
  89. package/ietf/namespace.d.ts +6 -0
  90. package/ietf/namespace.d.ts.map +1 -1
  91. package/ietf/namespace.js +27 -2
  92. package/ietf/namespace.js.map +1 -1
  93. package/ietf/object.d.ts +54 -6
  94. package/ietf/object.d.ts.map +1 -1
  95. package/ietf/object.js +178 -25
  96. package/ietf/object.js.map +1 -1
  97. package/ietf/parameters.d.ts +35 -10
  98. package/ietf/parameters.d.ts.map +1 -1
  99. package/ietf/parameters.js +236 -45
  100. package/ietf/parameters.js.map +1 -1
  101. package/ietf/priority.d.ts +5 -0
  102. package/ietf/priority.d.ts.map +1 -0
  103. package/ietf/priority.js +10 -0
  104. package/ietf/priority.js.map +1 -0
  105. package/ietf/properties.d.ts +13 -2
  106. package/ietf/properties.d.ts.map +1 -1
  107. package/ietf/properties.js +65 -8
  108. package/ietf/properties.js.map +1 -1
  109. package/ietf/publish.d.ts.map +1 -1
  110. package/ietf/publish.js +13 -3
  111. package/ietf/publish.js.map +1 -1
  112. package/ietf/publish_namespace.d.ts +16 -2
  113. package/ietf/publish_namespace.d.ts.map +1 -1
  114. package/ietf/publish_namespace.js +25 -6
  115. package/ietf/publish_namespace.js.map +1 -1
  116. package/ietf/publisher.d.ts +40 -7
  117. package/ietf/publisher.d.ts.map +1 -1
  118. package/ietf/publisher.js +751 -134
  119. package/ietf/publisher.js.map +1 -1
  120. package/ietf/request.js +1 -1
  121. package/ietf/request.js.map +1 -1
  122. package/ietf/solicit.d.ts +41 -0
  123. package/ietf/solicit.d.ts.map +1 -0
  124. package/ietf/solicit.js +47 -0
  125. package/ietf/solicit.js.map +1 -0
  126. package/ietf/subscribe.d.ts +41 -2
  127. package/ietf/subscribe.d.ts.map +1 -1
  128. package/ietf/subscribe.js +103 -22
  129. package/ietf/subscribe.js.map +1 -1
  130. package/ietf/subscribe_namespace.d.ts +21 -7
  131. package/ietf/subscribe_namespace.d.ts.map +1 -1
  132. package/ietf/subscribe_namespace.js +31 -14
  133. package/ietf/subscribe_namespace.js.map +1 -1
  134. package/ietf/subscriber.d.ts +20 -7
  135. package/ietf/subscriber.d.ts.map +1 -1
  136. package/ietf/subscriber.js +512 -142
  137. package/ietf/subscriber.js.map +1 -1
  138. package/ietf/version.d.ts +7 -1
  139. package/ietf/version.d.ts.map +1 -1
  140. package/ietf/version.js +7 -0
  141. package/ietf/version.js.map +1 -1
  142. package/index.d.ts +10 -5
  143. package/index.d.ts.map +1 -1
  144. package/index.js +10 -5
  145. package/index.js.map +1 -1
  146. package/internal.d.ts +29 -0
  147. package/internal.d.ts.map +1 -0
  148. package/internal.js +8 -0
  149. package/internal.js.map +1 -0
  150. package/lite/announce.d.ts +62 -15
  151. package/lite/announce.d.ts.map +1 -1
  152. package/lite/announce.js +230 -94
  153. package/lite/announce.js.map +1 -1
  154. package/lite/connection.d.ts +69 -20
  155. package/lite/connection.d.ts.map +1 -1
  156. package/lite/connection.js +128 -81
  157. package/lite/connection.js.map +1 -1
  158. package/lite/datagram.d.ts +23 -0
  159. package/lite/datagram.d.ts.map +1 -0
  160. package/lite/datagram.js +57 -0
  161. package/lite/datagram.js.map +1 -0
  162. package/lite/datagram_stream.d.ts +8 -0
  163. package/lite/datagram_stream.d.ts.map +1 -0
  164. package/lite/datagram_stream.js +44 -0
  165. package/lite/datagram_stream.js.map +1 -0
  166. package/lite/fetch.d.ts.map +1 -1
  167. package/lite/fetch.js +2 -2
  168. package/lite/fetch.js.map +1 -1
  169. package/lite/index.d.ts +3 -0
  170. package/lite/index.d.ts.map +1 -1
  171. package/lite/index.js +3 -0
  172. package/lite/index.js.map +1 -1
  173. package/lite/priority.d.ts +67 -0
  174. package/lite/priority.d.ts.map +1 -0
  175. package/lite/priority.js +114 -0
  176. package/lite/priority.js.map +1 -0
  177. package/lite/probe.d.ts +18 -2
  178. package/lite/probe.d.ts.map +1 -1
  179. package/lite/probe.js +24 -22
  180. package/lite/probe.js.map +1 -1
  181. package/lite/publisher.d.ts +14 -7
  182. package/lite/publisher.d.ts.map +1 -1
  183. package/lite/publisher.js +406 -84
  184. package/lite/publisher.js.map +1 -1
  185. package/lite/setup.d.ts +83 -6
  186. package/lite/setup.d.ts.map +1 -1
  187. package/lite/setup.js +203 -31
  188. package/lite/setup.js.map +1 -1
  189. package/lite/stream.d.ts +12 -3
  190. package/lite/stream.d.ts.map +1 -1
  191. package/lite/stream.js +7 -4
  192. package/lite/stream.js.map +1 -1
  193. package/lite/subscribe.d.ts +37 -23
  194. package/lite/subscribe.d.ts.map +1 -1
  195. package/lite/subscribe.js +70 -55
  196. package/lite/subscribe.js.map +1 -1
  197. package/lite/subscriber.d.ts +29 -23
  198. package/lite/subscriber.d.ts.map +1 -1
  199. package/lite/subscriber.js +689 -112
  200. package/lite/subscriber.js.map +1 -1
  201. package/lite/track.d.ts +25 -13
  202. package/lite/track.d.ts.map +1 -1
  203. package/lite/track.js +60 -35
  204. package/lite/track.js.map +1 -1
  205. package/lite/version.d.ts +39 -4
  206. package/lite/version.d.ts.map +1 -1
  207. package/lite/version.js +139 -17
  208. package/lite/version.js.map +1 -1
  209. package/mock.d.ts +53 -3
  210. package/mock.d.ts.map +1 -1
  211. package/mock.js +110 -21
  212. package/mock.js.map +1 -1
  213. package/{lite/origin.d.ts → origin.d.ts} +25 -1
  214. package/origin.d.ts.map +1 -0
  215. package/{lite/origin.js → origin.js} +25 -1
  216. package/origin.js.map +1 -0
  217. package/package.json +6 -4
  218. package/path.d.ts +67 -0
  219. package/path.d.ts.map +1 -1
  220. package/path.js +128 -0
  221. package/path.js.map +1 -1
  222. package/stream.d.ts +94 -4
  223. package/stream.d.ts.map +1 -1
  224. package/stream.js +128 -17
  225. package/stream.js.map +1 -1
  226. package/time.d.ts +109 -86
  227. package/time.d.ts.map +1 -1
  228. package/time.js +80 -30
  229. package/time.js.map +1 -1
  230. package/track.d.ts +298 -47
  231. package/track.d.ts.map +1 -1
  232. package/track.js +721 -109
  233. package/track.js.map +1 -1
  234. package/util/timeout.d.ts +5 -0
  235. package/util/timeout.d.ts.map +1 -0
  236. package/util/timeout.js +18 -0
  237. package/util/timeout.js.map +1 -0
  238. package/util/utf8.d.ts +3 -0
  239. package/util/utf8.d.ts.map +1 -0
  240. package/util/utf8.js +7 -0
  241. package/util/utf8.js.map +1 -0
  242. package/varint.d.ts +14 -3
  243. package/varint.d.ts.map +1 -1
  244. package/varint.js +23 -12
  245. package/varint.js.map +1 -1
  246. package/zod.d.ts +5 -5
  247. package/zod.d.ts.map +1 -1
  248. package/zod.js.map +1 -1
  249. package/bandwidth.d.ts +0 -11
  250. package/bandwidth.d.ts.map +0 -1
  251. package/bandwidth.js +0 -7
  252. package/bandwidth.js.map +0 -1
  253. package/connection/cert.test.d.ts +0 -2
  254. package/connection/cert.test.d.ts.map +0 -1
  255. package/connection/cert.test.js +0 -26
  256. package/connection/cert.test.js.map +0 -1
  257. package/connection/reload.test.d.ts +0 -2
  258. package/connection/reload.test.d.ts.map +0 -1
  259. package/connection/reload.test.js +0 -39
  260. package/connection/reload.test.js.map +0 -1
  261. package/group.test.d.ts +0 -2
  262. package/group.test.d.ts.map +0 -1
  263. package/group.test.js +0 -66
  264. package/group.test.js.map +0 -1
  265. package/ietf/aliases.test.d.ts +0 -2
  266. package/ietf/aliases.test.d.ts.map +0 -1
  267. package/ietf/aliases.test.js +0 -30
  268. package/ietf/aliases.test.js.map +0 -1
  269. package/ietf/ietf.test.d.ts +0 -2
  270. package/ietf/ietf.test.d.ts.map +0 -1
  271. package/ietf/ietf.test.js +0 -873
  272. package/ietf/ietf.test.js.map +0 -1
  273. package/integration.test.d.ts +0 -2
  274. package/integration.test.d.ts.map +0 -1
  275. package/integration.test.js +0 -90
  276. package/integration.test.js.map +0 -1
  277. package/lite/origin.d.ts.map +0 -1
  278. package/lite/origin.js.map +0 -1
  279. package/lite/subscriber.test.d.ts +0 -2
  280. package/lite/subscriber.test.d.ts.map +0 -1
  281. package/lite/subscriber.test.js +0 -30
  282. package/lite/subscriber.test.js.map +0 -1
  283. package/lite/track.test.d.ts +0 -2
  284. package/lite/track.test.d.ts.map +0 -1
  285. package/lite/track.test.js +0 -53
  286. package/lite/track.test.js.map +0 -1
  287. package/path.test.d.ts +0 -2
  288. package/path.test.d.ts.map +0 -1
  289. package/path.test.js +0 -169
  290. package/path.test.js.map +0 -1
  291. package/stream.test.d.ts +0 -2
  292. package/stream.test.d.ts.map +0 -1
  293. package/stream.test.js +0 -210
  294. package/stream.test.js.map +0 -1
  295. package/track.test.d.ts +0 -2
  296. package/track.test.d.ts.map +0 -1
  297. package/track.test.js +0 -56
  298. package/track.test.js.map +0 -1
  299. package/util/error.d.ts +0 -3
  300. package/util/error.d.ts.map +0 -1
  301. package/util/error.js +0 -9
  302. package/util/error.js.map +0 -1
  303. package/util/index.d.ts +0 -2
  304. package/util/index.d.ts.map +0 -1
  305. package/util/index.js +0 -3
  306. package/util/index.js.map +0 -1
  307. package/varint.test.d.ts +0 -2
  308. package/varint.test.d.ts.map +0 -1
  309. package/varint.test.js +0 -106
  310. package/varint.test.js.map +0 -1
package/README.md CHANGED
@@ -60,6 +60,7 @@ await quicheLoaded; //This is a promise, connect after it resolves
60
60
  - **[Publishing](examples/publish.ts)** - Publish data to a broadcast
61
61
  - **[Subscribing](examples/subscribe.ts)** - Subscribe to and receive broadcast data
62
62
  - **[Discovery](examples/discovery.ts)** - Discover broadcasts announced by the server
63
+ - **[Waiting](examples/wait.ts)** - Wait for one known broadcast to come online, and follow it
63
64
  - **[Server side usage](https://github.com/sb2702/webcodecs-examples/tree/main/src/moq-server)** - Publish from browser to a server
64
65
 
65
66
  ## License
package/announced.d.ts CHANGED
@@ -1,45 +1,135 @@
1
- import { Signal } from "@norskvideo/moq-signals";
1
+ /**
2
+ * Broadcast announcement streams: which broadcast paths are available under a prefix.
3
+ *
4
+ * @module
5
+ */
6
+ import { type GetPromise, type Getter, type GetterInit } from "@norskvideo/moq-signals";
7
+ import type * as broadcast from "./broadcast.js";
8
+ import type { Established } from "./connection/established.js";
2
9
  import * as Path from "./path.js";
3
10
  /**
4
11
  * The availability of a broadcast.
5
12
  *
6
13
  * @public
7
14
  */
8
- export interface AnnouncedEntry {
15
+ export interface Event {
16
+ /** Broadcast path relative to the prefix passed to `announced()`. */
9
17
  path: Path.Valid;
18
+ /** True when the broadcast is available, false when it was removed. */
10
19
  active: boolean;
11
20
  }
12
- /** Reactive backing state for an {@link Announced}: the pending queue plus a closed flag. */
13
- export declare class AnnouncedState {
14
- queue: Signal<AnnouncedEntry[]>;
15
- closed: Signal<boolean | Error>;
16
- }
17
21
  /**
18
- * Handles writing announcements to the announcement queue.
22
+ * The write side of an announcement stream.
19
23
  *
20
24
  * @public
21
25
  */
22
- export declare class Announced {
23
- /** Reactive backing state. */
24
- state: AnnouncedState;
26
+ export declare class Producer {
27
+ #private;
25
28
  /** Path prefix this stream is scoped to. */
26
29
  prefix: Path.Valid;
27
- /** Resolves with the abort error (or undefined) once closed. */
28
- readonly closed: Promise<Error | undefined>;
29
30
  constructor(prefix?: Path.Valid);
30
31
  /**
31
- * Writes an announcement to the queue.
32
- * @param announcement - The announcement to write
32
+ * Settles once the stream closes: `null` on a clean close, or the abort {@link Error}.
33
+ * Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.
34
+ */
35
+ get closed(): GetPromise<Error | null>;
36
+ /** A read handle for this announcement stream. */
37
+ consume(): Consumer;
38
+ /** Writes an announcement to the queue. */
39
+ append(event: Event): void;
40
+ /** Closes the writer. Idempotent. */
41
+ close(abort?: Error): void;
42
+ }
43
+ /**
44
+ * The read side of an announcement stream.
45
+ *
46
+ * Created internally: obtain one from {@link Producer.consume} or the connection's
47
+ * `announced(prefix)`.
48
+ *
49
+ * @public
50
+ */
51
+ export declare class Consumer {
52
+ #private;
53
+ /** Path prefix this stream is scoped to. */
54
+ prefix: Path.Valid;
55
+ private constructor();
56
+ /** Settles once the stream closes; see {@link Producer.closed}. */
57
+ get closed(): GetPromise<Error | null>;
58
+ /** Returns the next announcement. */
59
+ next(): Promise<Event | undefined>;
60
+ /** Closes the reader. Idempotent. */
61
+ close(abort?: Error): void;
62
+ }
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.
33
72
  */
34
- append(announcement: AnnouncedEntry): void;
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;
35
114
  /**
36
- * Closes the writer.
37
- * @param abort - If provided, throw this exception instead of returning undefined.
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.
38
122
  */
39
- close(abort?: Error): void;
123
+ readonly active: Getter<broadcast.Consumer | undefined>;
40
124
  /**
41
- * Returns the next announcement.
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>`.
42
130
  */
43
- next(): Promise<AnnouncedEntry | undefined>;
131
+ constructor({ connection, path }: BroadcastProps);
132
+ /** Closes the handle and the broadcast it currently holds. Idempotent. */
133
+ close(): void;
44
134
  }
45
135
  //# sourceMappingURL=announced.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"announced.d.ts","sourceRoot":"","sources":["../src/announced.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CAChB;AAED,6FAA6F;AAC7F,qBAAa,cAAc;IAC1B,KAAK,2BAAoC;IACzC,MAAM,0BAAsC;CAC5C;AAED;;;;GAIG;AACH,qBAAa,SAAS;IACrB,8BAA8B;IAC9B,KAAK,iBAAwB;IAE7B,4CAA4C;IAC5C,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC;IAEnB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAE5C,YAAY,MAAM,aAAe,EAShC;IAED;;;OAGG;IACH,MAAM,CAAC,YAAY,EAAE,cAAc,QAKlC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,QAKlB;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAWhD;CACD"}
1
+ {"version":3,"file":"announced.d.ts","sourceRoot":"","sources":["../src/announced.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAU,KAAK,UAAU,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAwB,MAAM,cAAc,CAAC;AAC3G,OAAO,KAAK,KAAK,SAAS,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;;GAIG;AACH,MAAM,WAAW,KAAK;IACrB,qEAAqE;IACrE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;IACjB,uEAAuE;IACvE,MAAM,EAAE,OAAO,CAAC;CAChB;AAiBD;;;;GAIG;AACH,qBAAa,QAAQ;;IACpB,4CAA4C;IAC5C,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC;IAInB,YAAY,MAAM,aAAe,EAEhC;IAED;;;OAGG;IACH,IAAI,MAAM,IAAI,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAErC;IAED,kDAAkD;IAClD,OAAO,IAAI,QAAQ,CAElB;IAED,2CAA2C;IAC3C,MAAM,CAAC,KAAK,EAAE,KAAK,QAKlB;IAED,qCAAqC;IACrC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,QAElB;CACD;AAMD;;;;;;;GAOG;AACH,qBAAa,QAAQ;;IACpB,4CAA4C;IAC5C,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC;IAInB,OAAO,eAGN;IAED,mEAAmE;IACnE,IAAI,MAAM,IAAI,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAErC;IAMD,qCAAqC;IAC/B,IAAI,IAAI,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAWvC;IAED,qCAAqC;IACrC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,QAElB;CACD;AAMD;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,UAAU,EAAE,UAAU,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAEhD,mCAAmC;IACnC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,SAAS;;IACrB,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;IAE1B;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IAKxD;;;;;;OAMG;IACH,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,cAAc,EA6E/C;IAED,0EAA0E;IAC1E,KAAK,SAEJ;CACD"}
package/announced.js CHANGED
@@ -1,70 +1,243 @@
1
1
  /* @ts-self-types="./announced.d.ts" */
2
- import { Signal } from "@norskvideo/moq-signals";
2
+ /**
3
+ * Broadcast announcement streams: which broadcast paths are available under a prefix.
4
+ *
5
+ * @module
6
+ */
7
+ import { Effect, getter, Once, Signal } from "@norskvideo/moq-signals";
3
8
  import * as Path from "./path.js";
4
- /** Reactive backing state for an {@link Announced}: the pending queue plus a closed flag. */
5
- export class AnnouncedState {
9
+ /** Reactive backing state shared by announcement producers and consumers. */
10
+ class AnnounceState {
6
11
  queue = new Signal([]);
7
- closed = new Signal(false);
12
+ closed = new Once();
13
+ }
14
+ // Once.set throws on a second settle, and both ends of a stream can close independently.
15
+ function closeState(state, abort) {
16
+ if (state.closed.peek() !== undefined)
17
+ return;
18
+ state.closed.set(abort ?? null);
19
+ state.queue.mutate((queue) => {
20
+ queue.length = 0;
21
+ });
8
22
  }
9
23
  /**
10
- * Handles writing announcements to the announcement queue.
24
+ * The write side of an announcement stream.
11
25
  *
12
26
  * @public
13
27
  */
14
- export class Announced {
15
- /** Reactive backing state. */
16
- state = new AnnouncedState();
28
+ export class Producer {
17
29
  /** Path prefix this stream is scoped to. */
18
30
  prefix;
19
- /** Resolves with the abort error (or undefined) once closed. */
20
- closed;
31
+ #state = new AnnounceState();
21
32
  constructor(prefix = Path.empty()) {
22
33
  this.prefix = prefix;
23
- this.closed = new Promise((resolve) => {
24
- const dispose = this.state.closed.subscribe((closed) => {
25
- if (!closed)
26
- return;
27
- resolve(closed instanceof Error ? closed : undefined);
28
- dispose();
29
- });
30
- });
31
34
  }
32
35
  /**
33
- * Writes an announcement to the queue.
34
- * @param announcement - The announcement to write
36
+ * Settles once the stream closes: `null` on a clean close, or the abort {@link Error}.
37
+ * Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.
35
38
  */
36
- append(announcement) {
37
- if (this.state.closed.peek())
38
- throw new Error("announced is closed");
39
- this.state.queue.mutate((queue) => {
40
- queue.push(announcement);
39
+ get closed() {
40
+ return this.#state.closed;
41
+ }
42
+ /** A read handle for this announcement stream. */
43
+ consume() {
44
+ return makeConsumer(this.prefix, this.#state);
45
+ }
46
+ /** Writes an announcement to the queue. */
47
+ append(event) {
48
+ if (this.#state.closed.peek() !== undefined)
49
+ throw new Error("announcements are closed");
50
+ this.#state.queue.mutate((queue) => {
51
+ queue.push(event);
41
52
  });
42
53
  }
43
- /**
44
- * Closes the writer.
45
- * @param abort - If provided, throw this exception instead of returning undefined.
46
- */
54
+ /** Closes the writer. Idempotent. */
47
55
  close(abort) {
48
- this.state.closed.set(abort ?? true);
49
- this.state.queue.mutate((queue) => {
50
- queue.length = 0;
51
- });
56
+ closeState(this.#state, abort);
52
57
  }
53
- /**
54
- * Returns the next announcement.
55
- */
58
+ }
59
+ // Constructs a Consumer from within this module without exposing a public constructor
60
+ // that would leak the unexported AnnounceState. Assigned in the class's static block.
61
+ let makeConsumer;
62
+ /**
63
+ * The read side of an announcement stream.
64
+ *
65
+ * Created internally: obtain one from {@link Producer.consume} or the connection's
66
+ * `announced(prefix)`.
67
+ *
68
+ * @public
69
+ */
70
+ export class Consumer {
71
+ /** Path prefix this stream is scoped to. */
72
+ prefix;
73
+ #state;
74
+ constructor(prefix, state) {
75
+ this.prefix = prefix;
76
+ this.#state = state;
77
+ }
78
+ /** Settles once the stream closes; see {@link Producer.closed}. */
79
+ get closed() {
80
+ return this.#state.closed;
81
+ }
82
+ static {
83
+ makeConsumer = (prefix, state) => new Consumer(prefix, state);
84
+ }
85
+ /** Returns the next announcement. */
56
86
  async next() {
57
87
  for (;;) {
58
- const announce = this.state.queue.peek().shift();
88
+ const announce = this.#state.queue.peek().shift();
59
89
  if (announce)
60
90
  return announce;
61
- const closed = this.state.closed.peek();
91
+ const closed = this.#state.closed.peek();
62
92
  if (closed instanceof Error)
63
93
  throw closed;
64
- if (closed)
94
+ if (closed !== undefined)
65
95
  return undefined;
66
- await Signal.race(this.state.queue, this.state.closed);
96
+ await Signal.race(this.#state.queue, this.#state.closed);
67
97
  }
68
98
  }
99
+ /** Closes the reader. Idempotent. */
100
+ close(abort) {
101
+ closeState(this.#state, abort);
102
+ }
103
+ }
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
+ }
69
242
  }
70
243
  //# 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,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAYlC,6FAA6F;AAC7F,MAAM,OAAO,cAAc;IAC1B,KAAK,GAAG,IAAI,MAAM,CAAmB,EAAE,CAAC,CAAC;IACzC,MAAM,GAAG,IAAI,MAAM,CAAkB,KAAK,CAAC,CAAC;CAC5C;AAED;;;;GAIG;AACH,MAAM,OAAO,SAAS;IACrB,8BAA8B;IAC9B,KAAK,GAAG,IAAI,cAAc,EAAE,CAAC;IAE7B,4CAA4C;IAC5C,MAAM,CAAa;IAEnB,gEAAgE;IACvD,MAAM,CAA6B;IAE5C,YAAY,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YACrC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE;gBACtD,IAAI,CAAC,MAAM;oBAAE,OAAO;gBACpB,OAAO,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;gBACtD,OAAO,EAAE,CAAC;YACX,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,YAA4B;QAClC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACrE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACjC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAa;QAClB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACjC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI;QACT,SAAS,CAAC;YACT,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YACjD,IAAI,QAAQ;gBAAE,OAAO,QAAQ,CAAC;YAE9B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACxC,IAAI,MAAM,YAAY,KAAK;gBAAE,MAAM,MAAM,CAAC;YAC1C,IAAI,MAAM;gBAAE,OAAO,SAAS,CAAC;YAE7B,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACxD,CAAC;IACF,CAAC;CACD","sourcesContent":["import { Signal } from \"@moq/signals\";\nimport * as Path from \"./path.js\";\n\n/**\n * The availability of a broadcast.\n *\n * @public\n */\nexport interface AnnouncedEntry {\n\tpath: Path.Valid;\n\tactive: boolean;\n}\n\n/** Reactive backing state for an {@link Announced}: the pending queue plus a closed flag. */\nexport class AnnouncedState {\n\tqueue = new Signal<AnnouncedEntry[]>([]);\n\tclosed = new Signal<boolean | Error>(false);\n}\n\n/**\n * Handles writing announcements to the announcement queue.\n *\n * @public\n */\nexport class Announced {\n\t/** Reactive backing state. */\n\tstate = new AnnouncedState();\n\n\t/** Path prefix this stream is scoped to. */\n\tprefix: Path.Valid;\n\n\t/** Resolves with the abort error (or undefined) once closed. */\n\treadonly closed: Promise<Error | undefined>;\n\n\tconstructor(prefix = Path.empty()) {\n\t\tthis.prefix = prefix;\n\t\tthis.closed = new Promise((resolve) => {\n\t\t\tconst dispose = this.state.closed.subscribe((closed) => {\n\t\t\t\tif (!closed) return;\n\t\t\t\tresolve(closed instanceof Error ? closed : undefined);\n\t\t\t\tdispose();\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Writes an announcement to the queue.\n\t * @param announcement - The announcement to write\n\t */\n\tappend(announcement: AnnouncedEntry) {\n\t\tif (this.state.closed.peek()) throw new Error(\"announced is closed\");\n\t\tthis.state.queue.mutate((queue) => {\n\t\t\tqueue.push(announcement);\n\t\t});\n\t}\n\n\t/**\n\t * Closes the writer.\n\t * @param abort - If provided, throw this exception instead of returning undefined.\n\t */\n\tclose(abort?: Error) {\n\t\tthis.state.closed.set(abort ?? true);\n\t\tthis.state.queue.mutate((queue) => {\n\t\t\tqueue.length = 0;\n\t\t});\n\t}\n\n\t/**\n\t * Returns the next announcement.\n\t */\n\tasync next(): Promise<AnnouncedEntry | 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) return undefined;\n\n\t\t\tawait Signal.race(this.state.queue, this.state.closed);\n\t\t}\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 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"]}
package/broadcast.d.ts CHANGED
@@ -1,38 +1,93 @@
1
- import { Signal } from "@norskvideo/moq-signals";
2
- import { Track } from "./track";
3
- /** A track a subscriber asked for, along with its requested delivery priority. */
4
- export interface TrackRequest {
5
- track: Track;
6
- priority: number;
7
- }
8
- /** Reactive backing state for a {@link Broadcast}: requested tracks plus a closed flag. */
9
- export declare class BroadcastState {
10
- requested: Signal<TrackRequest[]>;
11
- closed: Signal<boolean | Error>;
12
- }
13
1
  /**
14
- * Handles writing and managing tracks in a broadcast.
2
+ * Broadcast role handles: a named collection of tracks produced by a publisher.
3
+ *
4
+ * @module
5
+ */
6
+ import { type GetPromise } from "@norskvideo/moq-signals";
7
+ import type { Consumer as GroupConsumer } from "./group";
8
+ import * as track from "./track";
9
+ /**
10
+ * The write side of a broadcast.
15
11
  *
16
12
  * @public
17
13
  */
18
- export declare class Broadcast {
19
- /** Reactive backing state. */
20
- state: BroadcastState;
21
- /** Resolves with the abort error (or undefined) once closed. */
22
- readonly closed: Promise<Error | undefined>;
23
- constructor();
14
+ export declare class Producer implements track.Broadcast {
15
+ #private;
24
16
  /**
25
- * A track requested over the network.
17
+ * Settles once the broadcast closes: `null` on a clean close, or the abort {@link Error}.
18
+ * Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.
26
19
  */
27
- requested(): Promise<TrackRequest | undefined>;
20
+ get closed(): GetPromise<Error | null>;
21
+ /** A read handle for this broadcast. */
22
+ consume(): Consumer;
23
+ /** Return the next track requested by a peer. */
24
+ requested(): Promise<track.Request | undefined>;
25
+ /** Insert a track that is served directly, without an on-demand request round-trip. */
26
+ insertTrack(track: track.Producer): void;
27
+ /** Create a track, insert it into the broadcast, and return its producer. */
28
+ createTrack(name: string, info?: Partial<track.Info>): track.Producer;
29
+ /** Remove a statically inserted track by name. */
30
+ removeTrack(name: string): void;
31
+ /** Open a live subscription to a track. Used by the publishing wire layer. */
32
+ subscribe(name: string, options?: track.Subscription): track.Subscriber;
33
+ /** Resolve a track's immutable info. Used by the publishing wire layer. */
34
+ resolveTrackInfo(name: string): Promise<track.Info>;
35
+ /** Fetch a single group from the local retained window. Used by track handles. */
36
+ fetchGroup(name: string, sequence: number, options?: track.FetchGroupOptions): Promise<GroupConsumer>;
37
+ /** A lazy read handle for a track on this broadcast. */
38
+ track(name: string): track.Consumer;
39
+ /** Close the broadcast, optionally with an error to abort waiters. Idempotent. */
40
+ close(abort?: Error): void;
41
+ }
42
+ /**
43
+ * The read side of a broadcast.
44
+ *
45
+ * Created internally: obtain one from {@link Producer.consume} or the connection's
46
+ * `consume(path)`. The wire layers subclass it to resolve tracks over the network.
47
+ *
48
+ * @public
49
+ */
50
+ export declare class Consumer implements track.Broadcast {
51
+ #private;
52
+ protected constructor(state?: never);
28
53
  /**
29
- * Populates the provided track over the network.
54
+ * Settles once the broadcast closes: `null` on a clean close, or the abort {@link Error}.
55
+ * Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.
56
+ *
57
+ * Shared by every {@link clone}: it settles once the last handle closes. The subscribing
58
+ * wire layer peeks it to evict a closed entry from its per-path consume cache.
30
59
  */
31
- subscribe(name: string, priority: number): Track;
60
+ get closed(): GetPromise<Error | null>;
32
61
  /**
33
- * Closes the writer and all associated tracks.
62
+ * Return another handle to the same broadcast, reference-counted with this one.
34
63
  *
35
- * @param abort - If provided, throw this exception instead of returning undefined.
64
+ * Both handles read the same tracks and share one {@link closed} state; the broadcast
65
+ * closes only once *every* handle has {@link close}d. Used by the connection's per-path
66
+ * consume cache to share one subscription across callers. Subclasses that resolve info over
67
+ * the wire override this to preserve their type (see the wire layer's consumed broadcast).
68
+ */
69
+ clone(): Consumer;
70
+ protected shareState(): never;
71
+ /** Get a lazy handle for a track on this broadcast. Repeat subscriptions dedupe onto one upstream subscription. */
72
+ track(name: string): track.Consumer;
73
+ /** Open a live subscription to a track. Used by the subscribing wire layer. Repeat subscriptions to the same track share one upstream subscription. */
74
+ subscribe(name: string, options?: track.Subscription): track.Subscriber;
75
+ /** Return the next track requested by the local consumer. Used by the subscribing wire layer. */
76
+ requested(): Promise<track.Request | undefined>;
77
+ /**
78
+ * Resolve a track's immutable info. Used by track handles. This base resolves it from
79
+ * the local producers; the consuming wire layer overrides it to fetch over the wire.
80
+ */
81
+ resolveTrackInfo(name: string): Promise<track.Info>;
82
+ /**
83
+ * Fetch a single group by sequence. Used by track handles. This base serves from the
84
+ * local retained window; the consuming wire layer overrides it to fetch over the wire
85
+ * (or to reject when the transport has no FETCH).
86
+ */
87
+ fetchGroup(name: string, sequence: number, options?: track.FetchGroupOptions): Promise<GroupConsumer>;
88
+ /**
89
+ * Release this handle. The broadcast is closed (optionally with an error to abort waiters)
90
+ * once this was the last live handle; while other {@link clone}s remain open it stays live.
36
91
  */
37
92
  close(abort?: Error): void;
38
93
  }
@@ -1 +1 @@
1
- {"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,2FAA2F;AAC3F,qBAAa,cAAc;IAC1B,SAAS,yBAAkC;IAC3C,MAAM,0BAAsC;CAC5C;AAED;;;;GAIG;AACH,qBAAa,SAAS;IACrB,8BAA8B;IAC9B,KAAK,iBAAwB;IAE7B,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAE5C,cAQC;IAED;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAYnD;IAED;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,CAa/C;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,QAQlB;CACD"}
1
+ {"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAiIpC;;;;GAIG;AACH,qBAAa,QAAS,YAAW,KAAK,CAAC,SAAS;;IAG/C;;;OAGG;IACH,IAAI,MAAM,IAAI,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAErC;IAED,wCAAwC;IACxC,OAAO,IAAI,QAAQ,CAElB;IAED,iDAAiD;IAC3C,SAAS,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,CAWpD;IAED,uFAAuF;IACvF,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAiBvC;IAED,6EAA6E;IAC7E,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAM,GAAG,KAAK,CAAC,QAAQ,CAIxE;IAED,kDAAkD;IAClD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9B;IAED,8EAA8E;IAC9E,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,UAAU,CAEtE;IAED,2EAA2E;IAC3E,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAElD;IAED,kFAAkF;IAClF,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC,CAEpG;IAED,wDAAwD;IACxD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAElC;IAED,kFAAkF;IAClF,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,QAElB;CACD;AAMD;;;;;;;GAOG;AACH,qBAAa,QAAS,YAAW,KAAK,CAAC,SAAS;;IAM/C,SAAS,aAAa,KAAK,CAAC,EAAE,KAAK,EAAE;IAUrC;;;;;;OAMG;IACH,IAAI,MAAM,IAAI,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAErC;IAED;;;;;;;OAOG;IACH,KAAK,IAAI,QAAQ,CAEhB;IAID,SAAS,CAAC,UAAU,IAAI,KAAK,CAE5B;IAED,mHAAmH;IACnH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAElC;IAED,uJAAuJ;IACvJ,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,UAAU,CAEtE;IAED,iGAAiG;IAC3F,SAAS,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,CAWpD;IAED;;;OAGG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAElD;IAED;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC,CAEpG;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,QAKlB;CACD"}