@moqtap/codec 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/README.md +99 -95
  2. package/dist/chunk-4RIXXEII.js +1275 -0
  3. package/dist/chunk-4XYGE53S.cjs +698 -0
  4. package/dist/chunk-4YJANAXU.cjs +1109 -0
  5. package/dist/chunk-6AEHWULA.cjs +641 -0
  6. package/dist/chunk-7DUBLRXC.js +680 -0
  7. package/dist/{chunk-YBZD3DU5.cjs → chunk-7IVGHMKJ.cjs} +44 -42
  8. package/dist/chunk-BISI45MN.cjs +680 -0
  9. package/dist/{chunk-NLYTRGXA.cjs → chunk-BPNL5YFQ.cjs} +5 -5
  10. package/dist/chunk-CXDHOMHG.js +1097 -0
  11. package/dist/chunk-DUBCL3WF.cjs +749 -0
  12. package/dist/chunk-DWK5ZQZ4.js +642 -0
  13. package/dist/chunk-E6E3NQYU.js +680 -0
  14. package/dist/chunk-EFM5T7OM.js +698 -0
  15. package/dist/chunk-ENURAVHI.cjs +680 -0
  16. package/dist/{chunk-Z66WDWHI.js → chunk-FXZ2MYKJ.js} +127 -1
  17. package/dist/chunk-G26SJ6XS.cjs +1341 -0
  18. package/dist/chunk-G7GI7LJA.js +737 -0
  19. package/dist/chunk-GXEW4COZ.cjs +737 -0
  20. package/dist/chunk-HSVYF6XX.cjs +1361 -0
  21. package/dist/chunk-IBVM4DMJ.cjs +1097 -0
  22. package/dist/chunk-IV2H5CFI.cjs +1275 -0
  23. package/dist/chunk-IV2HRJVT.js +1198 -0
  24. package/dist/chunk-JSQM2MG3.js +680 -0
  25. package/dist/chunk-K4OLITS2.cjs +1055 -0
  26. package/dist/{chunk-UOBWHJA5.js → chunk-KFTCU2P6.js} +2 -3
  27. package/dist/chunk-LH4NTURO.js +1361 -0
  28. package/dist/{chunk-YTXLWKOR.js → chunk-MFAP7R6L.js} +1 -1
  29. package/dist/chunk-NGVE2RZT.js +1097 -0
  30. package/dist/chunk-NUX5BHWO.js +1341 -0
  31. package/dist/chunk-PJRA2TQ5.js +1055 -0
  32. package/dist/chunk-RVJAGE4S.cjs +1198 -0
  33. package/dist/{chunk-QYG6KGOV.cjs → chunk-RWQ43Z4F.cjs} +2 -3
  34. package/dist/chunk-RZHAPEXO.js +749 -0
  35. package/dist/chunk-ST24APEO.js +1109 -0
  36. package/dist/chunk-SYHW3FLI.cjs +642 -0
  37. package/dist/chunk-TLYNOOQP.cjs +432 -0
  38. package/dist/{chunk-IXHOBNXA.js → chunk-TMNGRIPL.js} +43 -41
  39. package/dist/chunk-UNS34PTA.cjs +680 -0
  40. package/dist/chunk-UR6JKS56.js +432 -0
  41. package/dist/{chunk-NPWHHWXT.cjs → chunk-UYXTY6ZQ.cjs} +127 -1
  42. package/dist/chunk-XUUCOLWU.cjs +1097 -0
  43. package/dist/chunk-YG5KJESI.js +641 -0
  44. package/dist/chunk-ZBKE2QRQ.js +1401 -0
  45. package/dist/chunk-ZSPO2GF2.cjs +1401 -0
  46. package/dist/codec-95k8CAu5.d.cts +35 -0
  47. package/dist/codec-AFuOxfsO.d.ts +60 -0
  48. package/dist/codec-B-UJ5Iow.d.cts +75 -0
  49. package/dist/codec-BC5jfvMb.d.ts +35 -0
  50. package/dist/codec-BECYPfY8.d.ts +35 -0
  51. package/dist/codec-BsPU1vNC.d.ts +39 -0
  52. package/dist/codec-BvpuF-6u.d.cts +39 -0
  53. package/dist/codec-C8jZI5Cx.d.cts +39 -0
  54. package/dist/codec-CAevkgf5.d.cts +33 -0
  55. package/dist/codec-CSUqCrRs.d.ts +39 -0
  56. package/dist/codec-C_HMXNK_.d.ts +33 -0
  57. package/dist/{codec-Bvr7rFtj.d.cts → codec-CpuvYTSV.d.cts} +1 -1
  58. package/dist/codec-D0x8-SCw.d.cts +35 -0
  59. package/dist/codec-D7ARhpG1.d.ts +75 -0
  60. package/dist/codec-DNAUGshO.d.cts +60 -0
  61. package/dist/codec-DPx_QNn0.d.ts +31 -0
  62. package/dist/{codec-B2mc2g3i.d.ts → codec-DRhCx_hw.d.ts} +1 -1
  63. package/dist/codec-Db7YPe3l.d.ts +31 -0
  64. package/dist/codec-axkJpb7D.d.cts +31 -0
  65. package/dist/codec-ujAbFaep.d.cts +31 -0
  66. package/dist/draft10-session.cjs +6 -0
  67. package/dist/draft10-session.d.cts +8 -0
  68. package/dist/draft10-session.d.ts +8 -0
  69. package/dist/draft10-session.js +6 -0
  70. package/dist/draft10.cjs +115 -0
  71. package/dist/draft10.d.cts +95 -0
  72. package/dist/draft10.d.ts +95 -0
  73. package/dist/draft10.js +115 -0
  74. package/dist/draft11-session.cjs +6 -0
  75. package/dist/draft11-session.d.cts +8 -0
  76. package/dist/draft11-session.d.ts +8 -0
  77. package/dist/draft11-session.js +6 -0
  78. package/dist/draft11.cjs +109 -0
  79. package/dist/draft11.d.cts +99 -0
  80. package/dist/draft11.d.ts +99 -0
  81. package/dist/draft11.js +109 -0
  82. package/dist/draft12-session.cjs +6 -0
  83. package/dist/draft12-session.d.cts +8 -0
  84. package/dist/draft12-session.d.ts +8 -0
  85. package/dist/draft12-session.js +6 -0
  86. package/dist/draft12.cjs +117 -0
  87. package/dist/draft12.d.cts +106 -0
  88. package/dist/draft12.d.ts +106 -0
  89. package/dist/draft12.js +117 -0
  90. package/dist/draft13-session.cjs +6 -0
  91. package/dist/draft13-session.d.cts +8 -0
  92. package/dist/draft13-session.d.ts +8 -0
  93. package/dist/draft13-session.js +6 -0
  94. package/dist/draft13.cjs +119 -0
  95. package/dist/draft13.d.cts +108 -0
  96. package/dist/draft13.d.ts +108 -0
  97. package/dist/draft13.js +119 -0
  98. package/dist/draft14-session.cjs +2 -2
  99. package/dist/draft14-session.d.cts +3 -3
  100. package/dist/draft14-session.d.ts +3 -3
  101. package/dist/draft14-session.js +1 -1
  102. package/dist/draft14.cjs +3 -3
  103. package/dist/draft14.d.cts +16 -4
  104. package/dist/draft14.d.ts +16 -4
  105. package/dist/draft14.js +2 -2
  106. package/dist/draft15-session.cjs +6 -0
  107. package/dist/draft15-session.d.cts +8 -0
  108. package/dist/draft15-session.d.ts +8 -0
  109. package/dist/draft15-session.js +6 -0
  110. package/dist/draft15.cjs +111 -0
  111. package/dist/draft15.d.cts +93 -0
  112. package/dist/draft15.d.ts +93 -0
  113. package/dist/draft15.js +111 -0
  114. package/dist/draft16-session.cjs +6 -0
  115. package/dist/draft16-session.d.cts +8 -0
  116. package/dist/draft16-session.d.ts +8 -0
  117. package/dist/draft16-session.js +6 -0
  118. package/dist/draft16.cjs +113 -0
  119. package/dist/draft16.d.cts +94 -0
  120. package/dist/draft16.d.ts +94 -0
  121. package/dist/draft16.js +113 -0
  122. package/dist/draft17-session.cjs +8 -0
  123. package/dist/draft17-session.d.cts +51 -0
  124. package/dist/draft17-session.d.ts +51 -0
  125. package/dist/draft17-session.js +8 -0
  126. package/dist/draft17.cjs +99 -0
  127. package/dist/draft17.d.cts +40 -0
  128. package/dist/draft17.d.ts +40 -0
  129. package/dist/draft17.js +99 -0
  130. package/dist/draft7-session.cjs +3 -3
  131. package/dist/draft7-session.d.cts +3 -3
  132. package/dist/draft7-session.d.ts +3 -3
  133. package/dist/draft7-session.js +2 -2
  134. package/dist/draft7.cjs +4 -4
  135. package/dist/draft7.d.cts +2 -2
  136. package/dist/draft7.d.ts +2 -2
  137. package/dist/draft7.js +2 -2
  138. package/dist/draft8-session.cjs +6 -0
  139. package/dist/draft8-session.d.cts +8 -0
  140. package/dist/draft8-session.d.ts +8 -0
  141. package/dist/draft8-session.js +6 -0
  142. package/dist/draft8.cjs +115 -0
  143. package/dist/draft8.d.cts +95 -0
  144. package/dist/draft8.d.ts +95 -0
  145. package/dist/draft8.js +115 -0
  146. package/dist/draft9-session.cjs +6 -0
  147. package/dist/draft9-session.d.cts +8 -0
  148. package/dist/draft9-session.d.ts +8 -0
  149. package/dist/draft9-session.js +6 -0
  150. package/dist/draft9.cjs +115 -0
  151. package/dist/draft9.d.cts +95 -0
  152. package/dist/draft9.d.ts +95 -0
  153. package/dist/draft9.js +115 -0
  154. package/dist/index.cjs +76 -4
  155. package/dist/index.d.cts +68 -5
  156. package/dist/index.d.ts +68 -5
  157. package/dist/index.js +76 -4
  158. package/dist/{session-types-DFjMk4HH.d.ts → session-types-CJIFbTPd.d.ts} +1 -1
  159. package/dist/{session-types-DW1RSZX_.d.cts → session-types-Cbq8IGCP.d.cts} +1 -1
  160. package/dist/session.cjs +5 -5
  161. package/dist/session.d.cts +3 -3
  162. package/dist/session.d.ts +3 -3
  163. package/dist/session.js +5 -5
  164. package/dist/types-4VxSL2Ho.d.cts +261 -0
  165. package/dist/types-4VxSL2Ho.d.ts +261 -0
  166. package/dist/types-B2afJZM-.d.cts +236 -0
  167. package/dist/types-B2afJZM-.d.ts +236 -0
  168. package/dist/types-Bg6QYNVt.d.cts +290 -0
  169. package/dist/types-Bg6QYNVt.d.ts +290 -0
  170. package/dist/types-C_1HrqBl.d.cts +306 -0
  171. package/dist/types-C_1HrqBl.d.ts +306 -0
  172. package/dist/types-Cw4WE9dh.d.cts +261 -0
  173. package/dist/types-Cw4WE9dh.d.ts +261 -0
  174. package/dist/types-D5gNQiDj.d.cts +261 -0
  175. package/dist/types-D5gNQiDj.d.ts +261 -0
  176. package/dist/types-DqCDFqgB.d.cts +230 -0
  177. package/dist/types-DqCDFqgB.d.ts +230 -0
  178. package/dist/types-ERexTpT8.d.cts +217 -0
  179. package/dist/types-ERexTpT8.d.ts +217 -0
  180. package/dist/{types-DPYE49t0.d.cts → types-QNXoxC9Y.d.cts} +2 -7
  181. package/dist/{types-DPYE49t0.d.ts → types-QNXoxC9Y.d.ts} +2 -7
  182. package/dist/types-r-CasCf1.d.cts +262 -0
  183. package/dist/types-r-CasCf1.d.ts +262 -0
  184. package/package.json +110 -2
  185. package/src/core/types.ts +24 -7
  186. package/src/drafts/draft07/codec.ts +49 -41
  187. package/src/drafts/draft07/rules.ts +1 -3
  188. package/src/drafts/draft08/codec.ts +1254 -0
  189. package/src/drafts/draft08/index.ts +125 -0
  190. package/src/drafts/draft08/messages.ts +72 -0
  191. package/src/drafts/draft08/rules.ts +91 -0
  192. package/src/drafts/draft08/session-fsm.ts +718 -0
  193. package/src/drafts/draft08/session.ts +26 -0
  194. package/src/drafts/draft08/types.ts +377 -0
  195. package/src/drafts/draft09/codec.ts +1235 -0
  196. package/src/drafts/draft09/index.ts +125 -0
  197. package/src/drafts/draft09/messages.ts +72 -0
  198. package/src/drafts/draft09/rules.ts +91 -0
  199. package/src/drafts/draft09/session-fsm.ts +718 -0
  200. package/src/drafts/draft09/session.ts +26 -0
  201. package/src/drafts/draft09/types.ts +376 -0
  202. package/src/drafts/draft10/codec.ts +1235 -0
  203. package/src/drafts/draft10/index.ts +125 -0
  204. package/src/drafts/draft10/messages.ts +72 -0
  205. package/src/drafts/draft10/rules.ts +91 -0
  206. package/src/drafts/draft10/session-fsm.ts +718 -0
  207. package/src/drafts/draft10/session.ts +26 -0
  208. package/src/drafts/draft10/types.ts +376 -0
  209. package/src/drafts/draft11/codec.ts +1198 -0
  210. package/src/drafts/draft11/index.ts +123 -0
  211. package/src/drafts/draft11/messages.ts +71 -0
  212. package/src/drafts/draft11/rules.ts +100 -0
  213. package/src/drafts/draft11/session-fsm.ts +758 -0
  214. package/src/drafts/draft11/session.ts +26 -0
  215. package/src/drafts/draft11/types.ts +375 -0
  216. package/src/drafts/draft12/codec.ts +1354 -0
  217. package/src/drafts/draft12/index.ts +130 -0
  218. package/src/drafts/draft12/messages.ts +84 -0
  219. package/src/drafts/draft12/rules.ts +106 -0
  220. package/src/drafts/draft12/session-fsm.ts +805 -0
  221. package/src/drafts/draft12/session.ts +26 -0
  222. package/src/drafts/draft12/types.ts +414 -0
  223. package/src/drafts/draft13/codec.ts +1438 -0
  224. package/src/drafts/draft13/index.ts +132 -0
  225. package/src/drafts/draft13/messages.ts +86 -0
  226. package/src/drafts/draft13/rules.ts +108 -0
  227. package/src/drafts/draft13/session-fsm.ts +819 -0
  228. package/src/drafts/draft13/session.ts +26 -0
  229. package/src/drafts/draft13/types.ts +433 -0
  230. package/src/drafts/draft14/session-fsm.ts +182 -1
  231. package/src/drafts/draft15/codec.ts +1661 -0
  232. package/src/drafts/draft15/index.ts +121 -0
  233. package/src/drafts/draft15/messages.ts +64 -0
  234. package/src/drafts/draft15/rules.ts +95 -0
  235. package/src/drafts/draft15/session-fsm.ts +687 -0
  236. package/src/drafts/draft15/session.ts +26 -0
  237. package/src/drafts/draft15/types.ts +336 -0
  238. package/src/drafts/draft16/codec.ts +1623 -0
  239. package/src/drafts/draft16/index.ts +123 -0
  240. package/src/drafts/draft16/messages.ts +67 -0
  241. package/src/drafts/draft16/rules.ts +96 -0
  242. package/src/drafts/draft16/session-fsm.ts +682 -0
  243. package/src/drafts/draft16/session.ts +26 -0
  244. package/src/drafts/draft16/types.ts +354 -0
  245. package/src/drafts/draft17/codec.ts +1621 -0
  246. package/src/drafts/draft17/index.ts +105 -0
  247. package/src/drafts/draft17/messages.ts +53 -0
  248. package/src/drafts/draft17/rules.ts +85 -0
  249. package/src/drafts/draft17/session-fsm.ts +437 -0
  250. package/src/drafts/draft17/session.ts +15 -0
  251. package/src/drafts/draft17/types.ts +310 -0
  252. package/src/index.ts +217 -2
@@ -0,0 +1,758 @@
1
+ import type {
2
+ AnnounceState,
3
+ FetchState,
4
+ ProtocolViolation,
5
+ SessionPhase,
6
+ SideEffect,
7
+ SubscriptionState,
8
+ TransitionResult,
9
+ ValidationResult,
10
+ } from "../../core/session-types.js";
11
+ import {
12
+ CLIENT_ONLY_MESSAGES,
13
+ getLegalIncoming,
14
+ getLegalOutgoing,
15
+ SERVER_ONLY_MESSAGES,
16
+ } from "./rules.js";
17
+ import type { Draft11Message, Draft11MessageType } from "./types.js";
18
+
19
+ function violation(
20
+ code: ProtocolViolation<Draft11MessageType>["code"],
21
+ message: string,
22
+ currentPhase: SessionPhase,
23
+ offendingMessage: Draft11MessageType,
24
+ ): ProtocolViolation<Draft11MessageType> {
25
+ return { code, message, currentPhase, offendingMessage };
26
+ }
27
+
28
+ export class Draft11SessionFSM {
29
+ private _phase: SessionPhase = "idle";
30
+ private _role: "client" | "server";
31
+ private _subscriptions = new Map<bigint, SubscriptionState>();
32
+ private _announces = new Map<string, AnnounceState>();
33
+ private _fetches = new Map<bigint, FetchState>();
34
+ private _requestIds = new Set<bigint>();
35
+
36
+ constructor(role: "client" | "server") {
37
+ this._role = role;
38
+ }
39
+
40
+ get phase(): SessionPhase {
41
+ return this._phase;
42
+ }
43
+ get role(): "client" | "server" {
44
+ return this._role;
45
+ }
46
+ get subscriptions(): ReadonlyMap<bigint, SubscriptionState> {
47
+ return this._subscriptions;
48
+ }
49
+ get announces(): ReadonlyMap<string, AnnounceState> {
50
+ return this._announces;
51
+ }
52
+ get fetches(): ReadonlyMap<bigint, FetchState> {
53
+ return this._fetches;
54
+ }
55
+
56
+ get legalOutgoing(): ReadonlySet<Draft11MessageType> {
57
+ return getLegalOutgoing(this._phase, this._role);
58
+ }
59
+
60
+ get legalIncoming(): ReadonlySet<Draft11MessageType> {
61
+ return getLegalIncoming(this._phase, this._role);
62
+ }
63
+
64
+ private checkRole(
65
+ message: Draft11Message,
66
+ direction: "inbound" | "outbound",
67
+ ): ProtocolViolation<Draft11MessageType> | null {
68
+ const senderRole =
69
+ direction === "outbound" ? this._role : this._role === "client" ? "server" : "client";
70
+
71
+ if (CLIENT_ONLY_MESSAGES.has(message.type) && senderRole !== "client") {
72
+ return violation(
73
+ "ROLE_VIOLATION",
74
+ `${message.type} can only be sent by client`,
75
+ this._phase,
76
+ message.type,
77
+ );
78
+ }
79
+ if (SERVER_ONLY_MESSAGES.has(message.type) && senderRole !== "server") {
80
+ return violation(
81
+ "ROLE_VIOLATION",
82
+ `${message.type} can only be sent by server`,
83
+ this._phase,
84
+ message.type,
85
+ );
86
+ }
87
+ return null;
88
+ }
89
+
90
+ private checkDuplicateRequestId(
91
+ requestId: bigint,
92
+ msgType: Draft11MessageType,
93
+ ): ProtocolViolation<Draft11MessageType> | null {
94
+ if (this._requestIds.has(requestId)) {
95
+ return violation(
96
+ "DUPLICATE_REQUEST_ID",
97
+ `Request ID ${requestId} already in use`,
98
+ this._phase,
99
+ msgType,
100
+ );
101
+ }
102
+ return null;
103
+ }
104
+
105
+ private checkKnownRequestId(
106
+ requestId: bigint,
107
+ msgType: Draft11MessageType,
108
+ ): ProtocolViolation<Draft11MessageType> | null {
109
+ if (!this._requestIds.has(requestId)) {
110
+ return violation(
111
+ "UNKNOWN_REQUEST_ID",
112
+ `No request with ID ${requestId}`,
113
+ this._phase,
114
+ msgType,
115
+ );
116
+ }
117
+ return null;
118
+ }
119
+
120
+ validateOutgoing(message: Draft11Message): ValidationResult<Draft11MessageType> {
121
+ const roleViolation = this.checkRole(message, "outbound");
122
+ if (roleViolation) return { ok: false, violation: roleViolation };
123
+
124
+ if (!this.legalOutgoing.has(message.type)) {
125
+ return {
126
+ ok: false,
127
+ violation: violation(
128
+ this._phase === "idle" || this._phase === "setup"
129
+ ? "MESSAGE_BEFORE_SETUP"
130
+ : "UNEXPECTED_MESSAGE",
131
+ `Cannot send ${message.type} in phase ${this._phase}`,
132
+ this._phase,
133
+ message.type,
134
+ ),
135
+ };
136
+ }
137
+ return { ok: true };
138
+ }
139
+
140
+ receive(message: Draft11Message): TransitionResult<Draft11MessageType> {
141
+ const roleViolation = this.checkRole(message, "inbound");
142
+ if (roleViolation) return { ok: false, violation: roleViolation };
143
+ return this.applyTransition(message, "inbound");
144
+ }
145
+
146
+ send(message: Draft11Message): TransitionResult<Draft11MessageType> {
147
+ const roleViolation = this.checkRole(message, "outbound");
148
+ if (roleViolation) return { ok: false, violation: roleViolation };
149
+ return this.applyTransition(message, "outbound");
150
+ }
151
+
152
+ private applyTransition(
153
+ message: Draft11Message,
154
+ direction: "inbound" | "outbound",
155
+ ): TransitionResult<Draft11MessageType> {
156
+ const sideEffects: SideEffect[] = [];
157
+
158
+ switch (message.type) {
159
+ case "client_setup":
160
+ return this.handleClientSetup(direction);
161
+ case "server_setup":
162
+ return this.handleServerSetup(direction);
163
+ case "goaway":
164
+ return this.handleGoAway(message, sideEffects);
165
+
166
+ // Subscribe lifecycle
167
+ case "subscribe":
168
+ return this.handleSubscribe(message, sideEffects);
169
+ case "subscribe_ok":
170
+ return this.handleSubscribeOk(message, sideEffects);
171
+ case "subscribe_error":
172
+ return this.handleSubscribeError(message, sideEffects);
173
+ case "subscribe_update":
174
+ return this.handleSubscribeUpdate(message, sideEffects);
175
+ case "subscribe_done":
176
+ return this.handleSubscribeDone(message, sideEffects);
177
+ case "unsubscribe":
178
+ return this.handleUnsubscribe(message, sideEffects);
179
+
180
+ // Announce lifecycle
181
+ case "announce":
182
+ return this.handleAnnounce(message, sideEffects);
183
+ case "announce_ok":
184
+ return this.handleAnnounceOk(message, sideEffects);
185
+ case "announce_error":
186
+ return this.handleAnnounceError(message, sideEffects);
187
+ case "unannounce":
188
+ return this.handleUnannounce(message, sideEffects);
189
+ case "announce_cancel":
190
+ return this.handleAnnounceCancel(message, sideEffects);
191
+
192
+ // Fetch lifecycle
193
+ case "fetch":
194
+ return this.handleFetch(message, sideEffects);
195
+ case "fetch_ok":
196
+ return this.handleFetchOk(message, sideEffects);
197
+ case "fetch_error":
198
+ return this.handleFetchError(message, sideEffects);
199
+ case "fetch_cancel":
200
+ return this.handleFetchCancel(message, sideEffects);
201
+
202
+ // Subscribe announces lifecycle
203
+ case "subscribe_announces":
204
+ return this.handleSubscribeAnnounces(message, sideEffects);
205
+ case "subscribe_announces_ok":
206
+ return this.handleSubscribeAnnouncesOk(message, sideEffects);
207
+ case "subscribe_announces_error":
208
+ return this.handleSubscribeAnnouncesError(message, sideEffects);
209
+
210
+ // Track status lifecycle
211
+ case "track_status_request":
212
+ return this.handleTrackStatusRequest(message, sideEffects);
213
+ case "track_status":
214
+ return this.handleTrackStatusResponse(message, sideEffects);
215
+
216
+ // Other ready-phase messages
217
+ default:
218
+ return this.handleReadyPhaseMessage(message);
219
+ }
220
+ }
221
+
222
+ private handleClientSetup(
223
+ direction: "inbound" | "outbound",
224
+ ): TransitionResult<Draft11MessageType> {
225
+ if (this._phase !== "idle") {
226
+ return {
227
+ ok: false,
228
+ violation: violation(
229
+ "SETUP_VIOLATION",
230
+ "CLIENT_SETUP already sent/received",
231
+ this._phase,
232
+ "client_setup",
233
+ ),
234
+ };
235
+ }
236
+ if (direction === "outbound" && this._role !== "client") {
237
+ return {
238
+ ok: false,
239
+ violation: violation(
240
+ "ROLE_VIOLATION",
241
+ "Only client can send CLIENT_SETUP",
242
+ this._phase,
243
+ "client_setup",
244
+ ),
245
+ };
246
+ }
247
+ this._phase = "setup";
248
+ return { ok: true, phase: this._phase, sideEffects: [] };
249
+ }
250
+
251
+ private handleServerSetup(
252
+ direction: "inbound" | "outbound",
253
+ ): TransitionResult<Draft11MessageType> {
254
+ if (this._phase !== "setup") {
255
+ return {
256
+ ok: false,
257
+ violation: violation(
258
+ "SETUP_VIOLATION",
259
+ "SERVER_SETUP before CLIENT_SETUP",
260
+ this._phase,
261
+ "server_setup",
262
+ ),
263
+ };
264
+ }
265
+ if (direction === "outbound" && this._role !== "server") {
266
+ return {
267
+ ok: false,
268
+ violation: violation(
269
+ "ROLE_VIOLATION",
270
+ "Only server can send SERVER_SETUP",
271
+ this._phase,
272
+ "server_setup",
273
+ ),
274
+ };
275
+ }
276
+ this._phase = "ready";
277
+ return { ok: true, phase: this._phase, sideEffects: [{ type: "session-ready" }] };
278
+ }
279
+
280
+ private handleGoAway(
281
+ message: Draft11Message,
282
+ sideEffects: SideEffect[],
283
+ ): TransitionResult<Draft11MessageType> {
284
+ if (this._phase !== "ready" && this._phase !== "draining") {
285
+ return {
286
+ ok: false,
287
+ violation: violation(
288
+ "UNEXPECTED_MESSAGE",
289
+ `GOAWAY not valid in phase ${this._phase}`,
290
+ this._phase,
291
+ "goaway",
292
+ ),
293
+ };
294
+ }
295
+ this._phase = "draining";
296
+ const goaway = message as import("./types.js").Draft11GoAway;
297
+ sideEffects.push({ type: "session-draining", goAwayUri: goaway.new_session_uri });
298
+ return { ok: true, phase: this._phase, sideEffects };
299
+ }
300
+
301
+ private requireReady(msgType: Draft11MessageType): ProtocolViolation<Draft11MessageType> | null {
302
+ if (this._phase !== "ready" && this._phase !== "draining") {
303
+ return violation(
304
+ this._phase === "idle" || this._phase === "setup"
305
+ ? "MESSAGE_BEFORE_SETUP"
306
+ : "UNEXPECTED_MESSAGE",
307
+ `${msgType} requires ready phase, current: ${this._phase}`,
308
+ this._phase,
309
+ msgType,
310
+ );
311
+ }
312
+ return null;
313
+ }
314
+
315
+ // ─── Subscribe lifecycle ───
316
+
317
+ private handleSubscribe(
318
+ message: Draft11Message,
319
+ sideEffects: SideEffect[],
320
+ ): TransitionResult<Draft11MessageType> {
321
+ const err = this.requireReady(message.type);
322
+ if (err) return { ok: false, violation: err };
323
+ const sub = message as import("./types.js").Draft11Subscribe;
324
+ const dupErr = this.checkDuplicateRequestId(sub.request_id, message.type);
325
+ if (dupErr) return { ok: false, violation: dupErr };
326
+ this._requestIds.add(sub.request_id);
327
+ this._subscriptions.set(sub.request_id, {
328
+ subscribeId: sub.request_id,
329
+ phase: "pending",
330
+ trackNamespace: sub.track_namespace,
331
+ trackName: sub.track_name,
332
+ });
333
+ return { ok: true, phase: this._phase, sideEffects };
334
+ }
335
+
336
+ private handleSubscribeOk(
337
+ message: Draft11Message,
338
+ sideEffects: SideEffect[],
339
+ ): TransitionResult<Draft11MessageType> {
340
+ const err = this.requireReady(message.type);
341
+ if (err) return { ok: false, violation: err };
342
+ const ok = message as import("./types.js").Draft11SubscribeOk;
343
+ const idErr = this.checkKnownRequestId(ok.request_id, message.type);
344
+ if (idErr) return { ok: false, violation: idErr };
345
+ const existing = this._subscriptions.get(ok.request_id);
346
+ if (!existing)
347
+ return {
348
+ ok: false,
349
+ violation: violation(
350
+ "UNKNOWN_REQUEST_ID",
351
+ `No subscription with request ID ${ok.request_id}`,
352
+ this._phase,
353
+ message.type,
354
+ ),
355
+ };
356
+ if (existing.phase !== "pending")
357
+ return {
358
+ ok: false,
359
+ violation: violation(
360
+ "STATE_VIOLATION",
361
+ `Subscription ${ok.request_id} is ${existing.phase}, not pending`,
362
+ this._phase,
363
+ message.type,
364
+ ),
365
+ };
366
+ this._subscriptions.set(ok.request_id, { ...existing, phase: "active" });
367
+ sideEffects.push({ type: "subscription-activated", subscribeId: ok.request_id });
368
+ return { ok: true, phase: this._phase, sideEffects };
369
+ }
370
+
371
+ private handleSubscribeError(
372
+ message: Draft11Message,
373
+ sideEffects: SideEffect[],
374
+ ): TransitionResult<Draft11MessageType> {
375
+ const err = this.requireReady(message.type);
376
+ if (err) return { ok: false, violation: err };
377
+ const subErr = message as import("./types.js").Draft11SubscribeError;
378
+ const idErr = this.checkKnownRequestId(subErr.request_id, message.type);
379
+ if (idErr) return { ok: false, violation: idErr };
380
+ const existing = this._subscriptions.get(subErr.request_id);
381
+ if (!existing)
382
+ return {
383
+ ok: false,
384
+ violation: violation(
385
+ "UNKNOWN_REQUEST_ID",
386
+ `No subscription with request ID ${subErr.request_id}`,
387
+ this._phase,
388
+ message.type,
389
+ ),
390
+ };
391
+ if (existing.phase !== "pending")
392
+ return {
393
+ ok: false,
394
+ violation: violation(
395
+ "STATE_VIOLATION",
396
+ `Subscription ${subErr.request_id} is ${existing.phase}, not pending`,
397
+ this._phase,
398
+ message.type,
399
+ ),
400
+ };
401
+ this._subscriptions.set(subErr.request_id, { ...existing, phase: "error" });
402
+ sideEffects.push({
403
+ type: "subscription-ended",
404
+ subscribeId: subErr.request_id,
405
+ reason: subErr.reason_phrase,
406
+ });
407
+ return { ok: true, phase: this._phase, sideEffects };
408
+ }
409
+
410
+ private handleSubscribeUpdate(
411
+ message: Draft11Message,
412
+ sideEffects: SideEffect[],
413
+ ): TransitionResult<Draft11MessageType> {
414
+ const err = this.requireReady(message.type);
415
+ if (err) return { ok: false, violation: err };
416
+ const update = message as import("./types.js").Draft11SubscribeUpdate;
417
+ const idErr = this.checkKnownRequestId(update.request_id, message.type);
418
+ if (idErr) return { ok: false, violation: idErr };
419
+ const existing = this._subscriptions.get(update.request_id);
420
+ if (!existing)
421
+ return {
422
+ ok: false,
423
+ violation: violation(
424
+ "UNKNOWN_REQUEST_ID",
425
+ `No subscription with request ID ${update.request_id}`,
426
+ this._phase,
427
+ message.type,
428
+ ),
429
+ };
430
+ if (existing.phase !== "active")
431
+ return {
432
+ ok: false,
433
+ violation: violation(
434
+ "STATE_VIOLATION",
435
+ `Subscription ${update.request_id} is ${existing.phase}, not active`,
436
+ this._phase,
437
+ message.type,
438
+ ),
439
+ };
440
+ return { ok: true, phase: this._phase, sideEffects };
441
+ }
442
+
443
+ private handleSubscribeDone(
444
+ message: Draft11Message,
445
+ sideEffects: SideEffect[],
446
+ ): TransitionResult<Draft11MessageType> {
447
+ const err = this.requireReady(message.type);
448
+ if (err) return { ok: false, violation: err };
449
+ const done = message as import("./types.js").Draft11SubscribeDone;
450
+ const idErr = this.checkKnownRequestId(done.request_id, message.type);
451
+ if (idErr) return { ok: false, violation: idErr };
452
+ const existing = this._subscriptions.get(done.request_id);
453
+ if (!existing)
454
+ return {
455
+ ok: false,
456
+ violation: violation(
457
+ "UNKNOWN_REQUEST_ID",
458
+ `No subscription with request ID ${done.request_id}`,
459
+ this._phase,
460
+ message.type,
461
+ ),
462
+ };
463
+ this._subscriptions.set(done.request_id, { ...existing, phase: "done" });
464
+ sideEffects.push({
465
+ type: "subscription-ended",
466
+ subscribeId: done.request_id,
467
+ reason: done.reason_phrase,
468
+ });
469
+ return { ok: true, phase: this._phase, sideEffects };
470
+ }
471
+
472
+ private handleUnsubscribe(
473
+ message: Draft11Message,
474
+ sideEffects: SideEffect[],
475
+ ): TransitionResult<Draft11MessageType> {
476
+ const err = this.requireReady(message.type);
477
+ if (err) return { ok: false, violation: err };
478
+ const unsub = message as import("./types.js").Draft11Unsubscribe;
479
+ const idErr = this.checkKnownRequestId(unsub.request_id, message.type);
480
+ if (idErr) return { ok: false, violation: idErr };
481
+ const existing = this._subscriptions.get(unsub.request_id);
482
+ if (!existing)
483
+ return {
484
+ ok: false,
485
+ violation: violation(
486
+ "UNKNOWN_REQUEST_ID",
487
+ `No subscription with request ID ${unsub.request_id}`,
488
+ this._phase,
489
+ message.type,
490
+ ),
491
+ };
492
+ this._subscriptions.set(unsub.request_id, { ...existing, phase: "done" });
493
+ sideEffects.push({
494
+ type: "subscription-ended",
495
+ subscribeId: unsub.request_id,
496
+ reason: "unsubscribed",
497
+ });
498
+ return { ok: true, phase: this._phase, sideEffects };
499
+ }
500
+
501
+ // ─── Announce lifecycle ───
502
+
503
+ private handleAnnounce(
504
+ message: Draft11Message,
505
+ sideEffects: SideEffect[],
506
+ ): TransitionResult<Draft11MessageType> {
507
+ const err = this.requireReady(message.type);
508
+ if (err) return { ok: false, violation: err };
509
+ const ann = message as import("./types.js").Draft11Announce;
510
+ const nsKey = ann.track_namespace.join("/");
511
+ this._requestIds.add(ann.request_id);
512
+ this._announces.set(nsKey, { namespace: ann.track_namespace, phase: "pending" });
513
+ return { ok: true, phase: this._phase, sideEffects };
514
+ }
515
+
516
+ private handleAnnounceOk(
517
+ message: Draft11Message,
518
+ sideEffects: SideEffect[],
519
+ ): TransitionResult<Draft11MessageType> {
520
+ const err = this.requireReady(message.type);
521
+ if (err) return { ok: false, violation: err };
522
+ const ok = message as import("./types.js").Draft11AnnounceOk;
523
+ const idErr = this.checkKnownRequestId(ok.request_id, message.type);
524
+ if (idErr) return { ok: false, violation: idErr };
525
+ return { ok: true, phase: this._phase, sideEffects };
526
+ }
527
+
528
+ private handleAnnounceError(
529
+ message: Draft11Message,
530
+ sideEffects: SideEffect[],
531
+ ): TransitionResult<Draft11MessageType> {
532
+ const err = this.requireReady(message.type);
533
+ if (err) return { ok: false, violation: err };
534
+ const annErr = message as import("./types.js").Draft11AnnounceError;
535
+ const idErr = this.checkKnownRequestId(annErr.request_id, message.type);
536
+ if (idErr) return { ok: false, violation: idErr };
537
+ return { ok: true, phase: this._phase, sideEffects };
538
+ }
539
+
540
+ private handleUnannounce(
541
+ message: Draft11Message,
542
+ sideEffects: SideEffect[],
543
+ ): TransitionResult<Draft11MessageType> {
544
+ const err = this.requireReady(message.type);
545
+ if (err) return { ok: false, violation: err };
546
+ const unann = message as import("./types.js").Draft11Unannounce;
547
+ const nsKey = unann.track_namespace.join("/");
548
+ const existing = this._announces.get(nsKey);
549
+ if (existing) {
550
+ this._announces.delete(nsKey);
551
+ sideEffects.push({ type: "announce-ended", namespace: unann.track_namespace });
552
+ }
553
+ return { ok: true, phase: this._phase, sideEffects };
554
+ }
555
+
556
+ private handleAnnounceCancel(
557
+ message: Draft11Message,
558
+ sideEffects: SideEffect[],
559
+ ): TransitionResult<Draft11MessageType> {
560
+ const err = this.requireReady(message.type);
561
+ if (err) return { ok: false, violation: err };
562
+ const cancel = message as import("./types.js").Draft11AnnounceCancel;
563
+ const nsKey = cancel.track_namespace.join("/");
564
+ const existing = this._announces.get(nsKey);
565
+ if (existing) {
566
+ this._announces.delete(nsKey);
567
+ sideEffects.push({ type: "announce-ended", namespace: cancel.track_namespace });
568
+ }
569
+ return { ok: true, phase: this._phase, sideEffects };
570
+ }
571
+
572
+ // ─── Fetch lifecycle ───
573
+
574
+ private handleFetch(
575
+ message: Draft11Message,
576
+ sideEffects: SideEffect[],
577
+ ): TransitionResult<Draft11MessageType> {
578
+ const err = this.requireReady(message.type);
579
+ if (err) return { ok: false, violation: err };
580
+ const fetch = message as import("./types.js").Draft11Fetch;
581
+ const dupErr = this.checkDuplicateRequestId(fetch.request_id, message.type);
582
+ if (dupErr) return { ok: false, violation: dupErr };
583
+ this._requestIds.add(fetch.request_id);
584
+ this._fetches.set(fetch.request_id, { requestId: fetch.request_id, phase: "pending" });
585
+ return { ok: true, phase: this._phase, sideEffects };
586
+ }
587
+
588
+ private handleFetchOk(
589
+ message: Draft11Message,
590
+ sideEffects: SideEffect[],
591
+ ): TransitionResult<Draft11MessageType> {
592
+ const err = this.requireReady(message.type);
593
+ if (err) return { ok: false, violation: err };
594
+ const ok = message as import("./types.js").Draft11FetchOk;
595
+ const idErr = this.checkKnownRequestId(ok.request_id, message.type);
596
+ if (idErr) return { ok: false, violation: idErr };
597
+ const existing = this._fetches.get(ok.request_id);
598
+ if (!existing)
599
+ return {
600
+ ok: false,
601
+ violation: violation(
602
+ "UNKNOWN_REQUEST_ID",
603
+ `No fetch with request ID ${ok.request_id}`,
604
+ this._phase,
605
+ message.type,
606
+ ),
607
+ };
608
+ if (existing.phase !== "pending")
609
+ return {
610
+ ok: false,
611
+ violation: violation(
612
+ "STATE_VIOLATION",
613
+ `Fetch ${ok.request_id} is ${existing.phase}, not pending`,
614
+ this._phase,
615
+ message.type,
616
+ ),
617
+ };
618
+ this._fetches.set(ok.request_id, { ...existing, phase: "active" });
619
+ sideEffects.push({ type: "fetch-activated", requestId: ok.request_id });
620
+ return { ok: true, phase: this._phase, sideEffects };
621
+ }
622
+
623
+ private handleFetchError(
624
+ message: Draft11Message,
625
+ sideEffects: SideEffect[],
626
+ ): TransitionResult<Draft11MessageType> {
627
+ const err = this.requireReady(message.type);
628
+ if (err) return { ok: false, violation: err };
629
+ const fetchErr = message as import("./types.js").Draft11FetchError;
630
+ const idErr = this.checkKnownRequestId(fetchErr.request_id, message.type);
631
+ if (idErr) return { ok: false, violation: idErr };
632
+ const existing = this._fetches.get(fetchErr.request_id);
633
+ if (!existing)
634
+ return {
635
+ ok: false,
636
+ violation: violation(
637
+ "UNKNOWN_REQUEST_ID",
638
+ `No fetch with request ID ${fetchErr.request_id}`,
639
+ this._phase,
640
+ message.type,
641
+ ),
642
+ };
643
+ this._fetches.set(fetchErr.request_id, { ...existing, phase: "error" });
644
+ sideEffects.push({
645
+ type: "fetch-ended",
646
+ requestId: fetchErr.request_id,
647
+ reason: fetchErr.reason_phrase,
648
+ });
649
+ return { ok: true, phase: this._phase, sideEffects };
650
+ }
651
+
652
+ private handleFetchCancel(
653
+ message: Draft11Message,
654
+ sideEffects: SideEffect[],
655
+ ): TransitionResult<Draft11MessageType> {
656
+ const err = this.requireReady(message.type);
657
+ if (err) return { ok: false, violation: err };
658
+ const cancel = message as import("./types.js").Draft11FetchCancel;
659
+ const idErr = this.checkKnownRequestId(cancel.request_id, message.type);
660
+ if (idErr) return { ok: false, violation: idErr };
661
+ const existing = this._fetches.get(cancel.request_id);
662
+ if (!existing)
663
+ return {
664
+ ok: false,
665
+ violation: violation(
666
+ "UNKNOWN_REQUEST_ID",
667
+ `No fetch with request ID ${cancel.request_id}`,
668
+ this._phase,
669
+ message.type,
670
+ ),
671
+ };
672
+ this._fetches.set(cancel.request_id, { ...existing, phase: "cancelled" });
673
+ sideEffects.push({ type: "fetch-ended", requestId: cancel.request_id, reason: "cancelled" });
674
+ return { ok: true, phase: this._phase, sideEffects };
675
+ }
676
+
677
+ // ─── Subscribe announces lifecycle ───
678
+
679
+ private handleSubscribeAnnounces(
680
+ message: Draft11Message,
681
+ sideEffects: SideEffect[],
682
+ ): TransitionResult<Draft11MessageType> {
683
+ const err = this.requireReady(message.type);
684
+ if (err) return { ok: false, violation: err };
685
+ const sa = message as import("./types.js").Draft11SubscribeAnnounces;
686
+ const dupErr = this.checkDuplicateRequestId(sa.request_id, message.type);
687
+ if (dupErr) return { ok: false, violation: dupErr };
688
+ this._requestIds.add(sa.request_id);
689
+ return { ok: true, phase: this._phase, sideEffects };
690
+ }
691
+
692
+ private handleSubscribeAnnouncesOk(
693
+ message: Draft11Message,
694
+ sideEffects: SideEffect[],
695
+ ): TransitionResult<Draft11MessageType> {
696
+ const err = this.requireReady(message.type);
697
+ if (err) return { ok: false, violation: err };
698
+ const ok = message as import("./types.js").Draft11SubscribeAnnouncesOk;
699
+ const idErr = this.checkKnownRequestId(ok.request_id, message.type);
700
+ if (idErr) return { ok: false, violation: idErr };
701
+ return { ok: true, phase: this._phase, sideEffects };
702
+ }
703
+
704
+ private handleSubscribeAnnouncesError(
705
+ message: Draft11Message,
706
+ sideEffects: SideEffect[],
707
+ ): TransitionResult<Draft11MessageType> {
708
+ const err = this.requireReady(message.type);
709
+ if (err) return { ok: false, violation: err };
710
+ const saErr = message as import("./types.js").Draft11SubscribeAnnouncesError;
711
+ const idErr = this.checkKnownRequestId(saErr.request_id, message.type);
712
+ if (idErr) return { ok: false, violation: idErr };
713
+ return { ok: true, phase: this._phase, sideEffects };
714
+ }
715
+
716
+ // ─── Track status lifecycle ───
717
+
718
+ private handleTrackStatusRequest(
719
+ message: Draft11Message,
720
+ sideEffects: SideEffect[],
721
+ ): TransitionResult<Draft11MessageType> {
722
+ const err = this.requireReady(message.type);
723
+ if (err) return { ok: false, violation: err };
724
+ const tsr = message as import("./types.js").Draft11TrackStatusRequest;
725
+ const dupErr = this.checkDuplicateRequestId(tsr.request_id, message.type);
726
+ if (dupErr) return { ok: false, violation: dupErr };
727
+ this._requestIds.add(tsr.request_id);
728
+ return { ok: true, phase: this._phase, sideEffects };
729
+ }
730
+
731
+ private handleTrackStatusResponse(
732
+ message: Draft11Message,
733
+ sideEffects: SideEffect[],
734
+ ): TransitionResult<Draft11MessageType> {
735
+ const err = this.requireReady(message.type);
736
+ if (err) return { ok: false, violation: err };
737
+ const ts = message as import("./types.js").Draft11TrackStatus;
738
+ const idErr = this.checkKnownRequestId(ts.request_id, message.type);
739
+ if (idErr) return { ok: false, violation: idErr };
740
+ return { ok: true, phase: this._phase, sideEffects };
741
+ }
742
+
743
+ // ─── Generic ready-phase handler ───
744
+
745
+ private handleReadyPhaseMessage(message: Draft11Message): TransitionResult<Draft11MessageType> {
746
+ const err = this.requireReady(message.type);
747
+ if (err) return { ok: false, violation: err };
748
+ return { ok: true, phase: this._phase, sideEffects: [] };
749
+ }
750
+
751
+ reset(): void {
752
+ this._phase = "idle";
753
+ this._subscriptions.clear();
754
+ this._announces.clear();
755
+ this._fetches.clear();
756
+ this._requestIds.clear();
757
+ }
758
+ }