@norskvideo/moq-net 0.1.8 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/README.md +2 -2
  2. package/announce.d.ts +7 -0
  3. package/announce.d.ts.map +1 -0
  4. package/announce.js +8 -0
  5. package/announce.js.map +1 -0
  6. package/announced.d.ts +49 -91
  7. package/announced.d.ts.map +1 -1
  8. package/announced.js +21 -156
  9. package/announced.js.map +1 -1
  10. package/bandwidth.d.ts +163 -0
  11. package/bandwidth.d.ts.map +1 -0
  12. package/bandwidth.js +304 -0
  13. package/bandwidth.js.map +1 -0
  14. package/bandwidth_api.d.ts +7 -0
  15. package/bandwidth_api.d.ts.map +1 -0
  16. package/bandwidth_api.js +8 -0
  17. package/bandwidth_api.js.map +1 -0
  18. package/broadcast.d.ts +44 -35
  19. package/broadcast.d.ts.map +1 -1
  20. package/broadcast.js +104 -60
  21. package/broadcast.js.map +1 -1
  22. package/connection/accept.d.ts +16 -1
  23. package/connection/accept.d.ts.map +1 -1
  24. package/connection/accept.js +52 -28
  25. package/connection/accept.js.map +1 -1
  26. package/connection/browser.d.ts.map +1 -1
  27. package/connection/browser.js +9 -7
  28. package/connection/browser.js.map +1 -1
  29. package/connection/connect.d.ts +30 -6
  30. package/connection/connect.d.ts.map +1 -1
  31. package/connection/connect.js +110 -54
  32. package/connection/connect.js.map +1 -1
  33. package/connection/established.d.ts +17 -21
  34. package/connection/established.d.ts.map +1 -1
  35. package/connection/established.js.map +1 -1
  36. package/connection/forward.d.ts +2 -0
  37. package/connection/forward.d.ts.map +1 -0
  38. package/connection/forward.js +173 -0
  39. package/connection/forward.js.map +1 -0
  40. package/connection/handshake.d.ts +1 -0
  41. package/connection/handshake.d.ts.map +1 -1
  42. package/connection/handshake.js +5 -2
  43. package/connection/handshake.js.map +1 -1
  44. package/connection/index.d.ts +5 -5
  45. package/connection/index.d.ts.map +1 -1
  46. package/connection/index.js +4 -5
  47. package/connection/index.js.map +1 -1
  48. package/connection/pool.d.ts +186 -0
  49. package/connection/pool.d.ts.map +1 -0
  50. package/connection/pool.js +361 -0
  51. package/connection/pool.js.map +1 -0
  52. package/connection/reload.d.ts +14 -93
  53. package/connection/reload.d.ts.map +1 -1
  54. package/connection/reload.js +217 -81
  55. package/connection/reload.js.map +1 -1
  56. package/connection/stats.d.ts +3 -26
  57. package/connection/stats.d.ts.map +1 -1
  58. package/connection/stats.js.map +1 -1
  59. package/connection/transport.d.ts +0 -7
  60. package/connection/transport.d.ts.map +1 -1
  61. package/consume.d.ts +1 -43
  62. package/consume.d.ts.map +1 -1
  63. package/consume.js +1 -1
  64. package/consume.js.map +1 -1
  65. package/error.d.ts +180 -29
  66. package/error.d.ts.map +1 -1
  67. package/error.js +331 -16
  68. package/error.js.map +1 -1
  69. package/errors.d.ts +7 -0
  70. package/errors.d.ts.map +1 -0
  71. package/errors.js +8 -0
  72. package/errors.js.map +1 -0
  73. package/group.d.ts +9 -43
  74. package/group.d.ts.map +1 -1
  75. package/group.js +284 -69
  76. package/group.js.map +1 -1
  77. package/hop.d.ts +115 -0
  78. package/hop.d.ts.map +1 -0
  79. package/hop.js +119 -0
  80. package/hop.js.map +1 -0
  81. package/ietf/adapter.d.ts +5 -1
  82. package/ietf/adapter.d.ts.map +1 -1
  83. package/ietf/adapter.js +105 -60
  84. package/ietf/adapter.js.map +1 -1
  85. package/ietf/aliases.d.ts +1 -78
  86. package/ietf/aliases.d.ts.map +1 -1
  87. package/ietf/cluster.d.ts +9 -123
  88. package/ietf/cluster.d.ts.map +1 -1
  89. package/ietf/cluster.js +84 -44
  90. package/ietf/cluster.js.map +1 -1
  91. package/ietf/connection.d.ts +6 -72
  92. package/ietf/connection.d.ts.map +1 -1
  93. package/ietf/connection.js +62 -56
  94. package/ietf/connection.js.map +1 -1
  95. package/ietf/error.d.ts +11 -0
  96. package/ietf/error.d.ts.map +1 -0
  97. package/ietf/error.js +193 -0
  98. package/ietf/error.js.map +1 -0
  99. package/ietf/fetch.d.ts +7 -20
  100. package/ietf/fetch.d.ts.map +1 -1
  101. package/ietf/fetch.js +52 -22
  102. package/ietf/fetch.js.map +1 -1
  103. package/ietf/filter.d.ts +2 -0
  104. package/ietf/filter.d.ts.map +1 -1
  105. package/ietf/filter.js +10 -1
  106. package/ietf/filter.js.map +1 -1
  107. package/ietf/goaway.d.ts.map +1 -1
  108. package/ietf/goaway.js +22 -5
  109. package/ietf/goaway.js.map +1 -1
  110. package/ietf/hidden.d.ts +2 -0
  111. package/ietf/hidden.d.ts.map +1 -0
  112. package/ietf/hidden.js +30 -0
  113. package/ietf/hidden.js.map +1 -0
  114. package/ietf/index.d.ts +2 -0
  115. package/ietf/index.d.ts.map +1 -1
  116. package/ietf/index.js +2 -0
  117. package/ietf/index.js.map +1 -1
  118. package/ietf/object.d.ts +15 -8
  119. package/ietf/object.d.ts.map +1 -1
  120. package/ietf/object.js +51 -36
  121. package/ietf/object.js.map +1 -1
  122. package/ietf/parameters.d.ts +14 -2
  123. package/ietf/parameters.d.ts.map +1 -1
  124. package/ietf/parameters.js +97 -29
  125. package/ietf/parameters.js.map +1 -1
  126. package/ietf/properties.d.ts +1 -0
  127. package/ietf/properties.d.ts.map +1 -1
  128. package/ietf/properties.js +14 -0
  129. package/ietf/properties.js.map +1 -1
  130. package/ietf/publish.d.ts +19 -2
  131. package/ietf/publish.d.ts.map +1 -1
  132. package/ietf/publish.js +40 -6
  133. package/ietf/publish.js.map +1 -1
  134. package/ietf/publish_namespace.d.ts +25 -0
  135. package/ietf/publish_namespace.d.ts.map +1 -1
  136. package/ietf/publish_namespace.js +63 -0
  137. package/ietf/publish_namespace.js.map +1 -1
  138. package/ietf/publisher.d.ts +1 -82
  139. package/ietf/publisher.d.ts.map +1 -1
  140. package/ietf/publisher.js +483 -235
  141. package/ietf/publisher.js.map +1 -1
  142. package/ietf/solicit.d.ts +1 -40
  143. package/ietf/solicit.d.ts.map +1 -1
  144. package/ietf/subscribe.d.ts +8 -6
  145. package/ietf/subscribe.d.ts.map +1 -1
  146. package/ietf/subscribe.js +33 -27
  147. package/ietf/subscribe.js.map +1 -1
  148. package/ietf/subscribe_namespace.d.ts +8 -2
  149. package/ietf/subscribe_namespace.d.ts.map +1 -1
  150. package/ietf/subscribe_namespace.js +18 -8
  151. package/ietf/subscribe_namespace.js.map +1 -1
  152. package/ietf/subscriber.d.ts +1 -65
  153. package/ietf/subscriber.d.ts.map +1 -1
  154. package/ietf/subscriber.js +337 -123
  155. package/ietf/subscriber.js.map +1 -1
  156. package/ietf/token.d.ts +2 -0
  157. package/ietf/token.d.ts.map +1 -0
  158. package/ietf/token.js +99 -0
  159. package/ietf/token.js.map +1 -0
  160. package/ietf/track.d.ts +4 -0
  161. package/ietf/track.d.ts.map +1 -1
  162. package/ietf/track.js +6 -20
  163. package/ietf/track.js.map +1 -1
  164. package/ietf/version.d.ts +12 -1
  165. package/ietf/version.d.ts.map +1 -1
  166. package/ietf/version.js +13 -0
  167. package/ietf/version.js.map +1 -1
  168. package/index.d.ts +12 -7
  169. package/index.d.ts.map +1 -1
  170. package/index.js +10 -5
  171. package/index.js.map +1 -1
  172. package/internal.d.ts +115 -1
  173. package/internal.d.ts.map +1 -1
  174. package/internal.js +108 -0
  175. package/internal.js.map +1 -1
  176. package/lite/announce.d.ts +60 -10
  177. package/lite/announce.d.ts.map +1 -1
  178. package/lite/announce.js +176 -31
  179. package/lite/announce.js.map +1 -1
  180. package/lite/connection.d.ts +9 -59
  181. package/lite/connection.d.ts.map +1 -1
  182. package/lite/connection.js +34 -35
  183. package/lite/connection.js.map +1 -1
  184. package/lite/datagram.d.ts +3 -2
  185. package/lite/datagram.d.ts.map +1 -1
  186. package/lite/datagram.js +7 -8
  187. package/lite/datagram.js.map +1 -1
  188. package/lite/fetch.d.ts +15 -1
  189. package/lite/fetch.d.ts.map +1 -1
  190. package/lite/fetch.js +39 -7
  191. package/lite/fetch.js.map +1 -1
  192. package/lite/goaway.d.ts.map +1 -1
  193. package/lite/goaway.js +7 -1
  194. package/lite/goaway.js.map +1 -1
  195. package/lite/group.d.ts +30 -12
  196. package/lite/group.d.ts.map +1 -1
  197. package/lite/group.js +68 -26
  198. package/lite/group.js.map +1 -1
  199. package/lite/message.d.ts +2 -2
  200. package/lite/message.d.ts.map +1 -1
  201. package/lite/message.js +14 -5
  202. package/lite/message.js.map +1 -1
  203. package/lite/priority.d.ts +1 -61
  204. package/lite/priority.d.ts.map +1 -1
  205. package/lite/priority.js +4 -5
  206. package/lite/priority.js.map +1 -1
  207. package/lite/publisher.d.ts +1 -69
  208. package/lite/publisher.d.ts.map +1 -1
  209. package/lite/publisher.js +630 -262
  210. package/lite/publisher.js.map +1 -1
  211. package/lite/setup.d.ts +8 -8
  212. package/lite/setup.d.ts.map +1 -1
  213. package/lite/setup.js +33 -30
  214. package/lite/setup.js.map +1 -1
  215. package/lite/subscribe.d.ts +71 -17
  216. package/lite/subscribe.d.ts.map +1 -1
  217. package/lite/subscribe.js +205 -53
  218. package/lite/subscribe.js.map +1 -1
  219. package/lite/subscriber.d.ts +15 -56
  220. package/lite/subscriber.d.ts.map +1 -1
  221. package/lite/subscriber.js +451 -247
  222. package/lite/subscriber.js.map +1 -1
  223. package/lite/track.d.ts +4 -10
  224. package/lite/track.d.ts.map +1 -1
  225. package/lite/track.js +34 -29
  226. package/lite/track.js.map +1 -1
  227. package/lite/version.d.ts +42 -6
  228. package/lite/version.d.ts.map +1 -1
  229. package/lite/version.js +126 -10
  230. package/lite/version.js.map +1 -1
  231. package/origin.d.ts +256 -29
  232. package/origin.d.ts.map +1 -1
  233. package/origin.js +1427 -37
  234. package/origin.js.map +1 -1
  235. package/package.json +8 -3
  236. package/path.d.ts +25 -7
  237. package/path.d.ts.map +1 -1
  238. package/path.js +5 -3
  239. package/path.js.map +1 -1
  240. package/stream.d.ts +73 -14
  241. package/stream.d.ts.map +1 -1
  242. package/stream.js +372 -141
  243. package/stream.js.map +1 -1
  244. package/tail.d.ts +18 -0
  245. package/tail.d.ts.map +1 -0
  246. package/tail.js +167 -0
  247. package/tail.js.map +1 -0
  248. package/time.d.ts +15 -2
  249. package/time.d.ts.map +1 -1
  250. package/time.js +28 -9
  251. package/time.js.map +1 -1
  252. package/track.d.ts +211 -83
  253. package/track.d.ts.map +1 -1
  254. package/track.js +816 -205
  255. package/track.js.map +1 -1
  256. package/util/abort.d.ts +2 -0
  257. package/util/abort.d.ts.map +1 -0
  258. package/util/abort.js +20 -0
  259. package/util/abort.js.map +1 -0
  260. package/util/log.d.ts +5 -0
  261. package/util/log.d.ts.map +1 -0
  262. package/util/log.js +17 -0
  263. package/util/log.js.map +1 -0
  264. package/util/u64.d.ts +39 -0
  265. package/util/u64.d.ts.map +1 -0
  266. package/util/u64.js +83 -0
  267. package/util/u64.js.map +1 -0
  268. package/util/varint.d.ts +29 -0
  269. package/util/varint.d.ts.map +1 -0
  270. package/util/varint.js +198 -0
  271. package/util/varint.js.map +1 -0
  272. package/varint.d.ts +10 -6
  273. package/varint.d.ts.map +1 -1
  274. package/varint.js +40 -237
  275. package/varint.js.map +1 -1
  276. package/wire.d.ts +80 -0
  277. package/wire.d.ts.map +1 -0
  278. package/wire.js +32 -0
  279. package/wire.js.map +1 -0
  280. package/zod.d.ts +1 -1
  281. package/zod.d.ts.map +1 -1
  282. package/zod.js.map +1 -1
  283. package/mock.d.ts +0 -66
  284. package/mock.d.ts.map +0 -1
  285. package/mock.js +0 -243
  286. package/mock.js.map +0 -1
package/lite/group.js CHANGED
@@ -1,46 +1,88 @@
1
1
  /* @ts-self-types="./group.d.ts" */
2
+ import { race } from "@norskvideo/moq-signals";
3
+ import * as Time from "../time.js";
2
4
  import * as Message from "./message.js";
5
+ import { hasFrameBounds } from "./version.js";
3
6
  export class Group {
4
7
  subscribe;
5
8
  sequence;
6
- constructor(subscribe, sequence) {
9
+ /**
10
+ * The index of the first frame on this stream within the group.
11
+ *
12
+ * 0 (the common case) means the stream carries the group from its beginning. A
13
+ * higher value means the leading frames are not here, either because the
14
+ * subscription started partway into the group or because the publisher only holds
15
+ * the tail. Lite-06+; older versions always start at 0.
16
+ */
17
+ frameStart;
18
+ constructor({ subscribe, sequence, frameStart = 0, }) {
7
19
  this.subscribe = subscribe;
8
20
  this.sequence = sequence;
21
+ this.frameStart = frameStart;
9
22
  }
10
- async #encode(w) {
23
+ async #encode(w, version) {
11
24
  await w.u62(this.subscribe);
12
25
  await w.u53(this.sequence);
26
+ if (hasFrameBounds(version)) {
27
+ await w.u53(this.frameStart);
28
+ }
29
+ else if (this.frameStart !== 0) {
30
+ // The peer would number the frames from 0 and silently misalign the group.
31
+ throw new Error("frame offsets not supported for this version");
32
+ }
13
33
  }
14
- static async #decode(r) {
15
- return new Group(await r.u62(), await r.u53());
34
+ static async #decode(r, version) {
35
+ const subscribe = await r.u62();
36
+ const sequence = await r.u53();
37
+ const frameStart = hasFrameBounds(version) ? await r.u53() : 0;
38
+ return new Group({ subscribe, sequence, frameStart });
16
39
  }
17
- async encode(w) {
18
- return Message.encode(w, this.#encode.bind(this));
40
+ async encode(w, version) {
41
+ return Message.encode(w, (w) => this.#encode(w, version));
19
42
  }
20
- static async decode(r) {
21
- return Message.decode(r, Group.#decode);
43
+ static async decode(r, version) {
44
+ return Message.decode(r, (r) => Group.#decode(r, version));
22
45
  }
23
- static async decodeMaybe(r) {
24
- return Message.decodeMaybe(r, Group.#decode);
46
+ static async decodeMaybe(r, version) {
47
+ return Message.decodeMaybe(r, (r) => Group.#decode(r, version));
25
48
  }
26
49
  }
27
- export class Frame {
28
- payload;
29
- constructor(payload) {
30
- this.payload = payload;
31
- }
32
- async #encode(w) {
33
- await w.write(this.payload);
34
- }
35
- static async #decode(r) {
36
- const payload = await r.readAll();
37
- return new Frame(payload);
38
- }
39
- async encode(w) {
40
- return Message.encode(w, this.#encode.bind(this));
50
+ /** Decode an unsigned zigzag varint back to a signed delta (mirrors Rust `VarInt::to_zigzag`). */
51
+ function unzigzag(v) {
52
+ return (v >> 1n) ^ -(v & 1n);
53
+ }
54
+ /**
55
+ * A synchronous decode for one frame of a group or FETCH response stream.
56
+ *
57
+ * A non-zero `scale` means every frame is prefixed with a zigzag-delta timestamp (the lite-05
58
+ * FRAME format), decoded into a Timestamp at that scale. Scale 0 (pre-lite-05) carries no
59
+ * timestamp, so frames are wall-clock stamped on arrival.
60
+ */
61
+ export function frameDecoder(scale) {
62
+ if (scale === 0) {
63
+ return (c) => ({ payload: c.read(c.u53()), timestamp: Time.Timestamp.now() });
41
64
  }
42
- static async decode(r) {
43
- return Message.decode(r, Frame.#decode);
65
+ const timescale = Time.Timescale(scale);
66
+ let prevTs = 0n;
67
+ return (c) => {
68
+ const delta = unzigzag(c.u62());
69
+ const payload = c.read(c.u53());
70
+ // After the last read, so a decode that ran short and gets retried adds the delta once.
71
+ prevTs += delta;
72
+ return { payload, timestamp: new Time.Timestamp(Number(prevTs), timescale) };
73
+ };
74
+ }
75
+ /** Write a group stream's frames into `producer` until the stream ends or the producer closes. */
76
+ export async function readFrames(stream, producer, scale) {
77
+ const decode = frameDecoder(scale);
78
+ for (;;) {
79
+ // Every frame already buffered is written without an await, so the reader wakes once
80
+ // per batch rather than once per frame. Only the group's own stream ends it: a track
81
+ // that closes first has already closed (or aborted) this group through its cache.
82
+ const frame = stream.tryDecode(decode) ?? (await race([stream.decodeMaybe(decode), producer.closed]));
83
+ if (!frame || frame instanceof Error)
84
+ return;
85
+ producer.writeFrame(frame);
44
86
  }
45
87
  }
46
88
  //# sourceMappingURL=group.js.map
package/lite/group.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"group.js","sourceRoot":"","sources":["../../src/lite/group.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAExC,MAAM,OAAO,KAAK;IACjB,SAAS,CAAS;IAClB,QAAQ,CAAS;IAEjB,YAAY,SAAiB,EAAE,QAAgB;QAC9C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,CAAS;QACtB,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5B,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAS;QAC7B,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,CAAS;QACrB,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAS;QAC5B,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAS;QACjC,OAAO,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;CACD;AAED,MAAM,OAAO,KAAK;IACjB,OAAO,CAAa;IAEpB,YAAY,OAAmB;QAC9B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,CAAS;QACtB,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAS;QAC7B,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;QAClC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,CAAS;QACrB,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAS;QAC5B,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;CACD","sourcesContent":["import type { Reader, Writer } from \"../stream.ts\";\nimport * as Message from \"./message.ts\";\n\nexport class Group {\n\tsubscribe: bigint;\n\tsequence: number;\n\n\tconstructor(subscribe: bigint, sequence: number) {\n\t\tthis.subscribe = subscribe;\n\t\tthis.sequence = sequence;\n\t}\n\n\tasync #encode(w: Writer) {\n\t\tawait w.u62(this.subscribe);\n\t\tawait w.u53(this.sequence);\n\t}\n\n\tstatic async #decode(r: Reader): Promise<Group> {\n\t\treturn new Group(await r.u62(), await r.u53());\n\t}\n\n\tasync encode(w: Writer): Promise<void> {\n\t\treturn Message.encode(w, this.#encode.bind(this));\n\t}\n\n\tstatic async decode(r: Reader): Promise<Group> {\n\t\treturn Message.decode(r, Group.#decode);\n\t}\n\n\tstatic async decodeMaybe(r: Reader): Promise<Group | undefined> {\n\t\treturn Message.decodeMaybe(r, Group.#decode);\n\t}\n}\n\nexport class Frame {\n\tpayload: Uint8Array;\n\n\tconstructor(payload: Uint8Array) {\n\t\tthis.payload = payload;\n\t}\n\n\tasync #encode(w: Writer) {\n\t\tawait w.write(this.payload);\n\t}\n\n\tstatic async #decode(r: Reader): Promise<Frame> {\n\t\tconst payload = await r.readAll();\n\t\treturn new Frame(payload);\n\t}\n\n\tasync encode(w: Writer): Promise<void> {\n\t\treturn Message.encode(w, this.#encode.bind(this));\n\t}\n\n\tstatic async decode(r: Reader): Promise<Frame> {\n\t\treturn Message.decode(r, Frame.#decode);\n\t}\n}\n"]}
1
+ {"version":3,"file":"group.js","sourceRoot":"","sources":["../../src/lite/group.ts"],"names":[],"mappings":"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","sourcesContent":["import { race } from \"@moq/signals\";\nimport type * as netGroup from \"../group.ts\";\nimport type { Cursor, Reader, Writer } from \"../stream.ts\";\nimport * as Time from \"../time.ts\";\nimport * as Message from \"./message.ts\";\nimport { hasFrameBounds, type Version } from \"./version.ts\";\n\nexport class Group {\n\tsubscribe: bigint;\n\tsequence: number;\n\n\t/**\n\t * The index of the first frame on this stream within the group.\n\t *\n\t * 0 (the common case) means the stream carries the group from its beginning. A\n\t * higher value means the leading frames are not here, either because the\n\t * subscription started partway into the group or because the publisher only holds\n\t * the tail. Lite-06+; older versions always start at 0.\n\t */\n\tframeStart: number;\n\n\tconstructor({\n\t\tsubscribe,\n\t\tsequence,\n\t\tframeStart = 0,\n\t}: {\n\t\tsubscribe: bigint;\n\t\tsequence: number;\n\t\tframeStart?: number;\n\t}) {\n\t\tthis.subscribe = subscribe;\n\t\tthis.sequence = sequence;\n\t\tthis.frameStart = frameStart;\n\t}\n\n\tasync #encode(w: Writer, version: Version) {\n\t\tawait w.u62(this.subscribe);\n\t\tawait w.u53(this.sequence);\n\t\tif (hasFrameBounds(version)) {\n\t\t\tawait w.u53(this.frameStart);\n\t\t} else if (this.frameStart !== 0) {\n\t\t\t// The peer would number the frames from 0 and silently misalign the group.\n\t\t\tthrow new Error(\"frame offsets not supported for this version\");\n\t\t}\n\t}\n\n\tstatic async #decode(r: Reader, version: Version): Promise<Group> {\n\t\tconst subscribe = await r.u62();\n\t\tconst sequence = await r.u53();\n\t\tconst frameStart = hasFrameBounds(version) ? await r.u53() : 0;\n\t\treturn new Group({ subscribe, sequence, frameStart });\n\t}\n\n\tasync encode(w: Writer, version: Version): Promise<void> {\n\t\treturn Message.encode(w, (w) => this.#encode(w, version));\n\t}\n\n\tstatic async decode(r: Reader, version: Version): Promise<Group> {\n\t\treturn Message.decode(r, (r) => Group.#decode(r, version));\n\t}\n\n\tstatic async decodeMaybe(r: Reader, version: Version): Promise<Group | undefined> {\n\t\treturn Message.decodeMaybe(r, (r) => Group.#decode(r, version));\n\t}\n}\n\n/** Decode an unsigned zigzag varint back to a signed delta (mirrors Rust `VarInt::to_zigzag`). */\nfunction unzigzag(v: bigint): bigint {\n\treturn (v >> 1n) ^ -(v & 1n);\n}\n\n/**\n * A synchronous decode for one frame of a group or FETCH response stream.\n *\n * A non-zero `scale` means every frame is prefixed with a zigzag-delta timestamp (the lite-05\n * FRAME format), decoded into a Timestamp at that scale. Scale 0 (pre-lite-05) carries no\n * timestamp, so frames are wall-clock stamped on arrival.\n */\nexport function frameDecoder(scale: number): (c: Cursor) => netGroup.Frame {\n\tif (scale === 0) {\n\t\treturn (c) => ({ payload: c.read(c.u53()), timestamp: Time.Timestamp.now() });\n\t}\n\n\tconst timescale = Time.Timescale(scale);\n\tlet prevTs = 0n;\n\treturn (c) => {\n\t\tconst delta = unzigzag(c.u62());\n\t\tconst payload = c.read(c.u53());\n\t\t// After the last read, so a decode that ran short and gets retried adds the delta once.\n\t\tprevTs += delta;\n\t\treturn { payload, timestamp: new Time.Timestamp(Number(prevTs), timescale) };\n\t};\n}\n\n/** Write a group stream's frames into `producer` until the stream ends or the producer closes. */\nexport async function readFrames(stream: Reader, producer: netGroup.Producer, scale: number): Promise<void> {\n\tconst decode = frameDecoder(scale);\n\n\tfor (;;) {\n\t\t// Every frame already buffered is written without an await, so the reader wakes once\n\t\t// per batch rather than once per frame. Only the group's own stream ends it: a track\n\t\t// that closes first has already closed (or aborted) this group through its cache.\n\t\tconst frame = stream.tryDecode(decode) ?? (await race([stream.decodeMaybe(decode), producer.closed]));\n\t\tif (!frame || frame instanceof Error) return;\n\t\tproducer.writeFrame(frame);\n\t}\n}\n"]}
package/lite/message.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Reader, Writer } from "../stream";
2
- export declare function encode(writer: Writer, f: (w: Writer) => Promise<void>): Promise<void>;
3
- export declare function decode<T>(reader: Reader, f: (r: Reader) => Promise<T>): Promise<T>;
2
+ export declare function encode(writer: Writer, f: (w: Writer) => Promise<void>, max?: number): Promise<void>;
3
+ export declare function decode<T>(reader: Reader, f: (r: Reader) => Promise<T>, max?: number): Promise<T>;
4
4
  export declare function decodeMaybe<T>(reader: Reader, f: (r: Reader) => Promise<T>): Promise<T | undefined>;
5
5
  //# sourceMappingURL=message.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../src/lite/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE9C,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,iBAqC3E;AAGD,wBAAsB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAaxF;AAED,wBAAsB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAGzG"}
1
+ {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../src/lite/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAM9C,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,SAAW,iBA0C3F;AAGD,wBAAsB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,SAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAgBxG;AAED,wBAAsB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAGzG"}
package/lite/message.js CHANGED
@@ -1,6 +1,9 @@
1
1
  /* @ts-self-types="./message.d.ts" */
2
2
  import { Reader, Writer } from "../stream.js";
3
- export async function encode(writer, f) {
3
+ /** The largest message body either side accepts, matching the Rust implementation. */
4
+ const MAX_SIZE = 64 * 1024 * 1024;
5
+ // Encodes a message with a varint size prefix, refusing a body the peer would reject.
6
+ export async function encode(writer, f, max = MAX_SIZE) {
4
7
  let scratch = new Uint8Array();
5
8
  const temp = new Writer(new WritableStream({
6
9
  write(chunk) {
@@ -22,20 +25,26 @@ export async function encode(writer, f) {
22
25
  scratch.set(chunk, needed - chunk.byteLength);
23
26
  }
24
27
  },
25
- }));
28
+ }), writer.version);
26
29
  await f(temp);
27
30
  temp.close();
28
31
  await temp.closed;
32
+ if (scratch.byteLength > max) {
33
+ throw new Error(`message too large: ${scratch.byteLength} bytes (max ${max})`);
34
+ }
29
35
  await writer.u53(scratch.byteLength);
30
36
  if (scratch.byteLength > 0) {
31
37
  await writer.write(scratch);
32
38
  }
33
39
  }
34
- // Reads a message with a varint size prefix.
35
- export async function decode(reader, f) {
40
+ // Reads a message with a varint size prefix, refusing an oversized body before reading it.
41
+ export async function decode(reader, f, max = MAX_SIZE) {
36
42
  const size = await reader.u53();
43
+ if (size > max) {
44
+ throw new Error(`message too large: ${size} bytes (max ${max})`);
45
+ }
37
46
  const data = await reader.read(size);
38
- const limit = new Reader(undefined, data);
47
+ const limit = new Reader(undefined, data, reader.version);
39
48
  const msg = await f(limit);
40
49
  // Check that we consumed exactly the right number of bytes
41
50
  if (!(await limit.done())) {
@@ -1 +1 @@
1
- {"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/lite/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,MAAc,EAAE,CAA+B;IAC3E,IAAI,OAAO,GAAG,IAAI,UAAU,EAAE,CAAC;IAE/B,MAAM,IAAI,GAAG,IAAI,MAAM,CACtB,IAAI,cAAc,CAAC;QAClB,KAAK,CAAC,KAAiB;YACtB,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;YACrD,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;gBACxC,wCAAwC;gBACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;gBACjE,MAAM,SAAS,GAAG,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC;gBAC5C,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;gBAExD,yCAAyC;gBACzC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAExB,0CAA0C;gBAC1C,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;gBAE1C,OAAO,GAAG,UAAU,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACP,8BAA8B;gBAC9B,OAAO,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;gBACpD,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/C,CAAC;QACF,CAAC;KACD,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;IACd,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,MAAM,IAAI,CAAC,MAAM,CAAC;IAElB,MAAM,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,OAAO,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;AACF,CAAC;AAED,6CAA6C;AAC7C,MAAM,CAAC,KAAK,UAAU,MAAM,CAAI,MAAc,EAAE,CAA4B;IAC3E,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAErC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;IAE3B,2DAA2D;IAC3D,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAI,MAAc,EAAE,CAA4B;IAChF,IAAI,MAAM,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO;IAChC,OAAO,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC","sourcesContent":["import { Reader, Writer } from \"../stream.ts\";\n\nexport async function encode(writer: Writer, f: (w: Writer) => Promise<void>) {\n\tlet scratch = new Uint8Array();\n\n\tconst temp = new Writer(\n\t\tnew WritableStream({\n\t\t\twrite(chunk: Uint8Array) {\n\t\t\t\tconst needed = scratch.byteLength + chunk.byteLength;\n\t\t\t\tif (needed > scratch.buffer.byteLength) {\n\t\t\t\t\t// Resize the buffer to the needed size.\n\t\t\t\t\tconst capacity = Math.max(needed, scratch.buffer.byteLength * 2);\n\t\t\t\t\tconst newBuffer = new ArrayBuffer(capacity);\n\t\t\t\t\tconst newScratch = new Uint8Array(newBuffer, 0, needed);\n\n\t\t\t\t\t// Copy the old data into the new buffer.\n\t\t\t\t\tnewScratch.set(scratch);\n\n\t\t\t\t\t// Copy the new chunk into the new buffer.\n\t\t\t\t\tnewScratch.set(chunk, scratch.byteLength);\n\n\t\t\t\t\tscratch = newScratch;\n\t\t\t\t} else {\n\t\t\t\t\t// Copy chunk data into buffer\n\t\t\t\t\tscratch = new Uint8Array(scratch.buffer, 0, needed);\n\t\t\t\t\tscratch.set(chunk, needed - chunk.byteLength);\n\t\t\t\t}\n\t\t\t},\n\t\t}),\n\t);\n\n\tawait f(temp);\n\ttemp.close();\n\tawait temp.closed;\n\n\tawait writer.u53(scratch.byteLength);\n\tif (scratch.byteLength > 0) {\n\t\tawait writer.write(scratch);\n\t}\n}\n\n// Reads a message with a varint size prefix.\nexport async function decode<T>(reader: Reader, f: (r: Reader) => Promise<T>): Promise<T> {\n\tconst size = await reader.u53();\n\tconst data = await reader.read(size);\n\n\tconst limit = new Reader(undefined, data);\n\tconst msg = await f(limit);\n\n\t// Check that we consumed exactly the right number of bytes\n\tif (!(await limit.done())) {\n\t\tthrow new Error(\"Message decoding consumed too few bytes\");\n\t}\n\n\treturn msg;\n}\n\nexport async function decodeMaybe<T>(reader: Reader, f: (r: Reader) => Promise<T>): Promise<T | undefined> {\n\tif (await reader.done()) return;\n\treturn await decode(reader, f);\n}\n"]}
1
+ {"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/lite/message.ts"],"names":[],"mappings":"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","sourcesContent":["import { Reader, Writer } from \"../stream.ts\";\n\n/** The largest message body either side accepts, matching the Rust implementation. */\nconst MAX_SIZE = 64 * 1024 * 1024;\n\n// Encodes a message with a varint size prefix, refusing a body the peer would reject.\nexport async function encode(writer: Writer, f: (w: Writer) => Promise<void>, max = MAX_SIZE) {\n\tlet scratch = new Uint8Array();\n\n\tconst temp = new Writer(\n\t\tnew WritableStream({\n\t\t\twrite(chunk: Uint8Array) {\n\t\t\t\tconst needed = scratch.byteLength + chunk.byteLength;\n\t\t\t\tif (needed > scratch.buffer.byteLength) {\n\t\t\t\t\t// Resize the buffer to the needed size.\n\t\t\t\t\tconst capacity = Math.max(needed, scratch.buffer.byteLength * 2);\n\t\t\t\t\tconst newBuffer = new ArrayBuffer(capacity);\n\t\t\t\t\tconst newScratch = new Uint8Array(newBuffer, 0, needed);\n\n\t\t\t\t\t// Copy the old data into the new buffer.\n\t\t\t\t\tnewScratch.set(scratch);\n\n\t\t\t\t\t// Copy the new chunk into the new buffer.\n\t\t\t\t\tnewScratch.set(chunk, scratch.byteLength);\n\n\t\t\t\t\tscratch = newScratch;\n\t\t\t\t} else {\n\t\t\t\t\t// Copy chunk data into buffer\n\t\t\t\t\tscratch = new Uint8Array(scratch.buffer, 0, needed);\n\t\t\t\t\tscratch.set(chunk, needed - chunk.byteLength);\n\t\t\t\t}\n\t\t\t},\n\t\t}),\n\t\twriter.version,\n\t);\n\n\tawait f(temp);\n\ttemp.close();\n\tawait temp.closed;\n\n\tif (scratch.byteLength > max) {\n\t\tthrow new Error(`message too large: ${scratch.byteLength} bytes (max ${max})`);\n\t}\n\n\tawait writer.u53(scratch.byteLength);\n\tif (scratch.byteLength > 0) {\n\t\tawait writer.write(scratch);\n\t}\n}\n\n// Reads a message with a varint size prefix, refusing an oversized body before reading it.\nexport async function decode<T>(reader: Reader, f: (r: Reader) => Promise<T>, max = MAX_SIZE): Promise<T> {\n\tconst size = await reader.u53();\n\tif (size > max) {\n\t\tthrow new Error(`message too large: ${size} bytes (max ${max})`);\n\t}\n\tconst data = await reader.read(size);\n\n\tconst limit = new Reader(undefined, data, reader.version);\n\tconst msg = await f(limit);\n\n\t// Check that we consumed exactly the right number of bytes\n\tif (!(await limit.done())) {\n\t\tthrow new Error(\"Message decoding consumed too few bytes\");\n\t}\n\n\treturn msg;\n}\n\nexport async function decodeMaybe<T>(reader: Reader, f: (r: Reader) => Promise<T>): Promise<T | undefined> {\n\tif (await reader.done()) return;\n\treturn await decode(reader, f);\n}\n"]}
@@ -3,65 +3,5 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import type { Writer } from "../stream";
7
- import type * as track from "../track";
8
- /**
9
- * What to rank a group stream by.
10
- *
11
- * @internal
12
- */
13
- export interface Rank {
14
- /** The subscriber's track priority, which outweighs everything below it. */
15
- priority: number;
16
- /**
17
- * Where the group sits in the queue its own subscription wants sent, 0 being the one to
18
- * send next. Defaults to 0, which is right for a stream that is the only one of its kind.
19
- */
20
- position?: number;
21
- }
22
- /**
23
- * The transport send order for a group, where HIGHER values are transmitted first.
24
- *
25
- * A higher `priority` always wins. Below it, a group is ranked by its `position` in its own
26
- * subscription rather than by its sequence, so two tracks at the same priority interleave:
27
- * each one's next group ties with the other's, whatever their group numbering. Comparing
28
- * sequences instead would let a track that had been running longer, or that numbered its
29
- * groups from a clock, starve one that started later.
30
- *
31
- * @internal
32
- */
33
- export declare function sendOrder({ priority, position }: Rank): number;
34
- /**
35
- * Ranks one subscription's group streams against each other, and re-ranks them whenever the
36
- * set changes or the subscription is updated.
37
- *
38
- * A group's rank is its position among the groups in flight, so which group goes first is
39
- * decided here rather than by arithmetic on sequence numbers. Newest-first by default, since
40
- * that is what live playback wants; an `ordered` subscription is playing through in sequence,
41
- * so it gets the oldest group first instead.
42
- *
43
- * One listener covers the whole subscription. A listener per group would instead pile up on
44
- * it, and a track that stalls with many groups open would trip the signals leak guard.
45
- *
46
- * @internal
47
- */
48
- export declare class Priority {
49
- #private;
50
- /** Follow `track`'s subscription until {@link close}. */
51
- constructor(track: track.Subscriber);
52
- /** The send order for a group at `sequence`, whether or not it has a stream yet. */
53
- rank(sequence: number): number;
54
- /** Rank a group's stream now, and again whenever the ranking changes, until {@link remove}. */
55
- add(stream: Writer, sequence: number): void;
56
- /** Stop ranking a finished group's stream, promoting whatever was queued behind it. */
57
- remove(stream: Writer): void;
58
- /**
59
- * Stop taking new groups, and release the subscription listener once the last one leaves.
60
- *
61
- * The track can finish while several groups are still draining, and those are the ones the
62
- * subscriber is still waiting on. They keep being promoted as the groups ahead of them
63
- * finish, rather than being stranded at whatever position they held when the track ended.
64
- */
65
- close(): void;
66
- }
6
+ export {};
67
7
  //# sourceMappingURL=priority.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"priority.d.ts","sourceRoot":"","sources":["../../src/lite/priority.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,KAAK,KAAK,MAAM,aAAa,CAAC;AAY1C;;;;GAIG;AACH,MAAM,WAAW,IAAI;IACpB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,QAAY,EAAE,EAAE,IAAI,GAAG,MAAM,CAElE;AAQD;;;;;;;;;;;;;GAaG;AACH,qBAAa,QAAQ;;IAMpB,yDAAyD;IACzD,YAAY,KAAK,EAAE,KAAK,CAAC,UAAU,EAGlC;IAED,oFAAoF;IACpF,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK7B;IAED,+FAA+F;IAC/F,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAMnC;IAED,uFAAuF;IACvF,MAAM,CAAC,MAAM,EAAE,MAAM,QAQpB;IAED;;;;;;OAMG;IACH,KAAK,SAGJ;CAqBD"}
1
+ {"version":3,"file":"priority.d.ts","sourceRoot":"","sources":["../../src/lite/priority.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
package/lite/priority.js CHANGED
@@ -36,9 +36,9 @@ function clamp(value, max) {
36
36
  * set changes or the subscription is updated.
37
37
  *
38
38
  * A group's rank is its position among the groups in flight, so which group goes first is
39
- * decided here rather than by arithmetic on sequence numbers. Newest-first by default, since
40
- * that is what live playback wants; an `ordered` subscription is playing through in sequence,
41
- * so it gets the oldest group first instead.
39
+ * decided here rather than by arithmetic on sequence numbers. Newest first, always: under
40
+ * congestion that sheds the backlog rather than the live edge, and a subscriber wanting
41
+ * sequence order reorders locally rather than asking the wire for it.
42
42
  *
43
43
  * One listener covers the whole subscription. A listener per group would instead pile up on
44
44
  * it, and a track that stalls with many groups open would trip the signals leak guard.
@@ -96,10 +96,9 @@ export class Priority {
96
96
  // Sequences are unique within a subscription, so a group already registered never counts
97
97
  // itself.
98
98
  #position(sequence) {
99
- const ordered = this.#track.subscription.peek()?.ordered ?? false;
100
99
  let ahead = 0;
101
100
  for (const other of this.#streams.values()) {
102
- if (ordered ? other < sequence : other > sequence)
101
+ if (other > sequence)
103
102
  ahead++;
104
103
  }
105
104
  return ahead;
@@ -1 +1 @@
1
- {"version":3,"file":"priority.js","sourceRoot":"","sources":["../../src/lite/priority.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Stream prioritization for moq-lite, mirroring the Rust `lite::priority` queue.\n *\n * @module\n */\n\nimport type { Dispose } from \"@moq/signals\";\nimport type { Writer } from \"../stream.ts\";\nimport type * as track from \"../track.ts\";\n\n// The transport compares send orders as a single number, so the two ranks are packed into\n// disjoint ranges: the track priority takes the high bits so it always dominates, and the\n// position below it. Group data stays negative so protocol streams at the transport's default\n// order of 0 always make progress. A u8 priority and 44-bit position fill the negative half of\n// the safe integer range exactly.\nconst GROUP_SPAN = 2 ** 44;\n\n/** The highest track priority the wire carries (a u8). */\nconst MAX_PRIORITY = 0xff;\n\n/**\n * What to rank a group stream by.\n *\n * @internal\n */\nexport interface Rank {\n\t/** The subscriber's track priority, which outweighs everything below it. */\n\tpriority: number;\n\n\t/**\n\t * Where the group sits in the queue its own subscription wants sent, 0 being the one to\n\t * send next. Defaults to 0, which is right for a stream that is the only one of its kind.\n\t */\n\tposition?: number;\n}\n\n/**\n * The transport send order for a group, where HIGHER values are transmitted first.\n *\n * A higher `priority` always wins. Below it, a group is ranked by its `position` in its own\n * subscription rather than by its sequence, so two tracks at the same priority interleave:\n * each one's next group ties with the other's, whatever their group numbering. Comparing\n * sequences instead would let a track that had been running longer, or that numbered its\n * groups from a clock, starve one that started later.\n *\n * @internal\n */\nexport function sendOrder({ priority, position = 0 }: Rank): number {\n\treturn -((MAX_PRIORITY - clamp(priority, MAX_PRIORITY)) * GROUP_SPAN + clamp(position, GROUP_SPAN - 1) + 1);\n}\n\n// Both ranks are bounded: the priority by its wire type, the position by the space left below\n// it. Anything outside is a caller bug rather than a value worth preserving.\nfunction clamp(value: number, max: number): number {\n\treturn Math.min(Math.max(Math.trunc(value), 0), max);\n}\n\n/**\n * Ranks one subscription's group streams against each other, and re-ranks them whenever the\n * set changes or the subscription is updated.\n *\n * A group's rank is its position among the groups in flight, so which group goes first is\n * decided here rather than by arithmetic on sequence numbers. Newest-first by default, since\n * that is what live playback wants; an `ordered` subscription is playing through in sequence,\n * so it gets the oldest group first instead.\n *\n * One listener covers the whole subscription. A listener per group would instead pile up on\n * it, and a track that stalls with many groups open would trip the signals leak guard.\n *\n * @internal\n */\nexport class Priority {\n\t#track: track.Subscriber;\n\t#streams = new Map<Writer, number>();\n\t#dispose: Dispose;\n\t#closed = false;\n\n\t/** Follow `track`'s subscription until {@link close}. */\n\tconstructor(track: track.Subscriber) {\n\t\tthis.#track = track;\n\t\tthis.#dispose = track.subscription.subscribe(() => this.#rerank());\n\t}\n\n\t/** The send order for a group at `sequence`, whether or not it has a stream yet. */\n\trank(sequence: number): number {\n\t\treturn sendOrder({\n\t\t\tpriority: this.#track.subscription.peek()?.priority ?? 0,\n\t\t\tposition: this.#position(sequence),\n\t\t});\n\t}\n\n\t/** Rank a group's stream now, and again whenever the ranking changes, until {@link remove}. */\n\tadd(stream: Writer, sequence: number) {\n\t\tthis.#streams.set(stream, sequence);\n\n\t\t// Also covers the stream itself: opening one can block on transport capacity, so an\n\t\t// update that landed while it did predates this registration.\n\t\tthis.#rerank();\n\t}\n\n\t/** Stop ranking a finished group's stream, promoting whatever was queued behind it. */\n\tremove(stream: Writer) {\n\t\tif (!this.#streams.delete(stream)) return;\n\n\t\tif (this.#closed && this.#streams.size === 0) {\n\t\t\tthis.#dispose();\n\t\t} else {\n\t\t\tthis.#rerank();\n\t\t}\n\t}\n\n\t/**\n\t * Stop taking new groups, and release the subscription listener once the last one leaves.\n\t *\n\t * The track can finish while several groups are still draining, and those are the ones the\n\t * subscriber is still waiting on. They keep being promoted as the groups ahead of them\n\t * finish, rather than being stranded at whatever position they held when the track ended.\n\t */\n\tclose() {\n\t\tthis.#closed = true;\n\t\tif (this.#streams.size === 0) this.#dispose();\n\t}\n\n\t// How many of this subscription's groups in flight should be sent before `sequence`.\n\t// Sequences are unique within a subscription, so a group already registered never counts\n\t// itself.\n\t#position(sequence: number): number {\n\t\tconst ordered = this.#track.subscription.peek()?.ordered ?? false;\n\n\t\tlet ahead = 0;\n\t\tfor (const other of this.#streams.values()) {\n\t\t\tif (ordered ? other < sequence : other > sequence) ahead++;\n\t\t}\n\t\treturn ahead;\n\t}\n\n\t#rerank() {\n\t\tconst priority = this.#track.subscription.peek()?.priority ?? 0;\n\t\tfor (const [stream, sequence] of this.#streams) {\n\t\t\tstream.setPriority(sendOrder({ priority, position: this.#position(sequence) }));\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"priority.js","sourceRoot":"","sources":["../../src/lite/priority.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Stream prioritization for moq-lite, mirroring the Rust `lite::priority` queue.\n *\n * @module\n */\n\nimport type { Dispose } from \"@moq/signals\";\nimport type { Writer } from \"../stream.ts\";\nimport type * as track from \"../track.ts\";\n\n// The transport compares send orders as a single number, so the two ranks are packed into\n// disjoint ranges: the track priority takes the high bits so it always dominates, and the\n// position below it. Group data stays negative so protocol streams at the transport's default\n// order of 0 always make progress. A u8 priority and 44-bit position fill the negative half of\n// the safe integer range exactly.\nconst GROUP_SPAN = 2 ** 44;\n\n/** The highest track priority the wire carries (a u8). */\nconst MAX_PRIORITY = 0xff;\n\n/**\n * What to rank a group stream by.\n *\n * @internal\n */\nexport interface Rank {\n\t/** The subscriber's track priority, which outweighs everything below it. */\n\tpriority: number;\n\n\t/**\n\t * Where the group sits in the queue its own subscription wants sent, 0 being the one to\n\t * send next. Defaults to 0, which is right for a stream that is the only one of its kind.\n\t */\n\tposition?: number;\n}\n\n/**\n * The transport send order for a group, where HIGHER values are transmitted first.\n *\n * A higher `priority` always wins. Below it, a group is ranked by its `position` in its own\n * subscription rather than by its sequence, so two tracks at the same priority interleave:\n * each one's next group ties with the other's, whatever their group numbering. Comparing\n * sequences instead would let a track that had been running longer, or that numbered its\n * groups from a clock, starve one that started later.\n *\n * @internal\n */\nexport function sendOrder({ priority, position = 0 }: Rank): number {\n\treturn -((MAX_PRIORITY - clamp(priority, MAX_PRIORITY)) * GROUP_SPAN + clamp(position, GROUP_SPAN - 1) + 1);\n}\n\n// Both ranks are bounded: the priority by its wire type, the position by the space left below\n// it. Anything outside is a caller bug rather than a value worth preserving.\nfunction clamp(value: number, max: number): number {\n\treturn Math.min(Math.max(Math.trunc(value), 0), max);\n}\n\n/**\n * Ranks one subscription's group streams against each other, and re-ranks them whenever the\n * set changes or the subscription is updated.\n *\n * A group's rank is its position among the groups in flight, so which group goes first is\n * decided here rather than by arithmetic on sequence numbers. Newest first, always: under\n * congestion that sheds the backlog rather than the live edge, and a subscriber wanting\n * sequence order reorders locally rather than asking the wire for it.\n *\n * One listener covers the whole subscription. A listener per group would instead pile up on\n * it, and a track that stalls with many groups open would trip the signals leak guard.\n *\n * @internal\n */\nexport class Priority {\n\t#track: track.Subscriber;\n\t#streams = new Map<Writer, number>();\n\t#dispose: Dispose;\n\t#closed = false;\n\n\t/** Follow `track`'s subscription until {@link close}. */\n\tconstructor(track: track.Subscriber) {\n\t\tthis.#track = track;\n\t\tthis.#dispose = track.subscription.subscribe(() => this.#rerank());\n\t}\n\n\t/** The send order for a group at `sequence`, whether or not it has a stream yet. */\n\trank(sequence: number): number {\n\t\treturn sendOrder({\n\t\t\tpriority: this.#track.subscription.peek()?.priority ?? 0,\n\t\t\tposition: this.#position(sequence),\n\t\t});\n\t}\n\n\t/** Rank a group's stream now, and again whenever the ranking changes, until {@link remove}. */\n\tadd(stream: Writer, sequence: number) {\n\t\tthis.#streams.set(stream, sequence);\n\n\t\t// Also covers the stream itself: opening one can block on transport capacity, so an\n\t\t// update that landed while it did predates this registration.\n\t\tthis.#rerank();\n\t}\n\n\t/** Stop ranking a finished group's stream, promoting whatever was queued behind it. */\n\tremove(stream: Writer) {\n\t\tif (!this.#streams.delete(stream)) return;\n\n\t\tif (this.#closed && this.#streams.size === 0) {\n\t\t\tthis.#dispose();\n\t\t} else {\n\t\t\tthis.#rerank();\n\t\t}\n\t}\n\n\t/**\n\t * Stop taking new groups, and release the subscription listener once the last one leaves.\n\t *\n\t * The track can finish while several groups are still draining, and those are the ones the\n\t * subscriber is still waiting on. They keep being promoted as the groups ahead of them\n\t * finish, rather than being stranded at whatever position they held when the track ended.\n\t */\n\tclose() {\n\t\tthis.#closed = true;\n\t\tif (this.#streams.size === 0) this.#dispose();\n\t}\n\n\t// How many of this subscription's groups in flight should be sent before `sequence`.\n\t// Sequences are unique within a subscription, so a group already registered never counts\n\t// itself.\n\t#position(sequence: number): number {\n\t\tlet ahead = 0;\n\t\tfor (const other of this.#streams.values()) {\n\t\t\tif (other > sequence) ahead++;\n\t\t}\n\t\treturn ahead;\n\t}\n\n\t#rerank() {\n\t\tconst priority = this.#track.subscription.peek()?.priority ?? 0;\n\t\tfor (const [stream, sequence] of this.#streams) {\n\t\t\tstream.setPriority(sendOrder({ priority, position: this.#position(sequence) }));\n\t\t}\n\t}\n}\n"]}
@@ -1,70 +1,2 @@
1
- import type * as broadcast from "../broadcast";
2
- import type { Origin } from "../origin";
3
- import * as Path from "../path";
4
- import { type Stream } from "../stream";
5
- import { type AnnounceRequest } from "./announce";
6
- import type { Fetch } from "./fetch";
7
- import { type Subscribe } from "./subscribe";
8
- import { type Track as TrackMessage } from "./track";
9
- import { Version } from "./version";
10
- /**
11
- * Handles publishing broadcasts and managing their lifecycle.
12
- *
13
- * @internal
14
- */
15
- export declare class Publisher {
16
- #private;
17
- readonly version: Version;
18
- readonly origin: Origin;
19
- /**
20
- * Creates a new Publisher instance.
21
- * @param quic - The WebTransport session to use
22
- * @param version - Negotiated protocol version
23
- * @param origin - Origin id shared with the Subscriber
24
- *
25
- * @internal
26
- */
27
- constructor(quic: WebTransport, version: Version, origin: Origin);
28
- /**
29
- * Publishes a broadcast with any associated tracks.
30
- * @param name - The broadcast to publish
31
- */
32
- publish(path: Path.Valid, broadcast: broadcast.Producer): void;
33
- /**
34
- * Handles an announce interest message.
35
- * @param msg - The announce interest message
36
- * @param stream - The stream to write announcements to
37
- *
38
- * @internal
39
- */
40
- runAnnounce(msg: AnnounceRequest, stream: Stream): Promise<void>;
41
- /**
42
- * Handles a subscribe message.
43
- * @param msg - The subscribe message
44
- * @param stream - The stream to write track data to
45
- *
46
- * @internal
47
- */
48
- runSubscribe(msg: Subscribe, stream: Stream): Promise<void>;
49
- /**
50
- * Handles a FETCH stream by serving one group as bare frame records (lite-05+).
51
- *
52
- * @internal
53
- */
54
- runFetch(msg: Fetch, stream: Stream): Promise<void>;
55
- /**
56
- * Answers a TRACK stream (0x6) with a single TRACK_INFO, then FINs.
57
- *
58
- * @internal
59
- */
60
- runTrackInfo(msg: TrackMessage, stream: Stream): Promise<void>;
61
- /**
62
- * Handles a probe stream by periodically reporting estimated bitrate.
63
- * @param stream - The probe bidi stream
64
- *
65
- * @internal
66
- */
67
- runProbe(stream: Stream): Promise<void>;
68
- close(): void;
69
- }
1
+ export {};
70
2
  //# sourceMappingURL=publisher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"publisher.d.ts","sourceRoot":"","sources":["../../src/lite/publisher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAGlD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,MAAM,EAAU,MAAM,cAAc,CAAC;AAGnD,OAAO,EAA4B,KAAK,eAAe,EAA2B,MAAM,eAAe,CAAC;AAGxG,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAIxC,OAAO,EAEN,KAAK,SAAS,EAKd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAiC,KAAK,KAAK,IAAI,YAAY,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,EAA2D,OAAO,EAAE,MAAM,cAAc,CAAC;AA4ChG;;;;GAIG;AACH,qBAAa,SAAS;;IAErB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAM1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAoBxB;;;;;;;OAOG;IACH,YAAY,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAU/D;IAED;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,CAAC,QAAQ,QActD;IAED;;;;;;OAMG;IACG,WAAW,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,iBA2GrD;IAED;;;;;;OAMG;IACG,YAAY,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,iBA6FhD;IAED;;;;OAIG;IACG,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,iBAkCxC;IAsFD;;;;OAIG;IACG,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,iBAUnD;IAgKD;;;;;OAKG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,iBAiF5B;IAED,KAAK,SAWJ;CACD"}
1
+ {"version":3,"file":"publisher.d.ts","sourceRoot":"","sources":["../../src/lite/publisher.ts"],"names":[],"mappings":""}