@moqtap/codec 0.1.0 → 0.1.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 (67) hide show
  1. package/dist/{chunk-YBSEOSSP.js → chunk-A27S7HW7.js} +5 -1
  2. package/dist/{chunk-5WFXFLL4.cjs → chunk-FUFTMAQD.cjs} +96 -63
  3. package/dist/{chunk-2NARXGVA.cjs → chunk-FWISIR26.cjs} +5 -1
  4. package/dist/{chunk-23YG7F46.js → chunk-IXHOBNXA.js} +117 -17
  5. package/dist/{chunk-3BSZ55L3.cjs → chunk-NLYTRGXA.cjs} +153 -19
  6. package/dist/{chunk-GDRGWFEK.cjs → chunk-NPWHHWXT.cjs} +249 -37
  7. package/dist/{chunk-IQPDRQVC.js → chunk-U2B3B42P.js} +62 -29
  8. package/dist/{chunk-WNTXF3DE.cjs → chunk-YBZD3DU5.cjs} +127 -27
  9. package/dist/{chunk-DC4L6ZIT.js → chunk-YTXLWKOR.js} +153 -19
  10. package/dist/{chunk-YPXLV5YK.js → chunk-Z66WDWHI.js} +249 -37
  11. package/dist/{codec-qPzfmLNu.d.ts → codec-B2mc2g3i.d.ts} +5 -5
  12. package/dist/{codec-CTvFtQQI.d.cts → codec-Bvr7rFtj.d.cts} +5 -5
  13. package/dist/draft14-session.cjs +2 -2
  14. package/dist/draft14-session.d.cts +4 -4
  15. package/dist/draft14-session.d.ts +4 -4
  16. package/dist/draft14-session.js +1 -1
  17. package/dist/draft14.cjs +4 -4
  18. package/dist/draft14.d.cts +15 -15
  19. package/dist/draft14.d.ts +15 -15
  20. package/dist/draft14.js +3 -3
  21. package/dist/draft7-session.cjs +2 -2
  22. package/dist/draft7-session.d.cts +3 -3
  23. package/dist/draft7-session.d.ts +3 -3
  24. package/dist/draft7-session.js +1 -1
  25. package/dist/draft7.cjs +5 -5
  26. package/dist/draft7.d.cts +10 -10
  27. package/dist/draft7.d.ts +10 -10
  28. package/dist/draft7.js +2 -2
  29. package/dist/index.cjs +6 -6
  30. package/dist/index.d.cts +6 -6
  31. package/dist/index.d.ts +6 -6
  32. package/dist/index.js +3 -3
  33. package/dist/{session-types-B9NIf7_F.d.ts → session-types-DFjMk4HH.d.ts} +20 -20
  34. package/dist/{session-types-CCo-oA-d.d.cts → session-types-DW1RSZX_.d.cts} +20 -20
  35. package/dist/session.cjs +4 -4
  36. package/dist/session.d.cts +3 -3
  37. package/dist/session.d.ts +3 -3
  38. package/dist/session.js +2 -2
  39. package/dist/{types-CIk5W10V.d.ts → types-BTFeKYCb.d.cts} +37 -37
  40. package/dist/{types-CIk5W10V.d.cts → types-BTFeKYCb.d.ts} +37 -37
  41. package/dist/{types-ClXELFGN.d.cts → types-DPYE49t0.d.cts} +36 -36
  42. package/dist/{types-ClXELFGN.d.ts → types-DPYE49t0.d.ts} +36 -36
  43. package/package.json +7 -7
  44. package/src/core/buffer-reader.ts +16 -9
  45. package/src/core/buffer-writer.ts +2 -2
  46. package/src/core/errors.ts +1 -1
  47. package/src/core/session-types.ts +28 -41
  48. package/src/core/types.ts +70 -70
  49. package/src/drafts/draft07/announce-fsm.ts +1 -1
  50. package/src/drafts/draft07/codec.ts +195 -86
  51. package/src/drafts/draft07/index.ts +43 -44
  52. package/src/drafts/draft07/messages.ts +1 -1
  53. package/src/drafts/draft07/parameters.ts +2 -2
  54. package/src/drafts/draft07/rules.ts +68 -37
  55. package/src/drafts/draft07/session-fsm.ts +330 -117
  56. package/src/drafts/draft07/session.ts +10 -10
  57. package/src/drafts/draft07/subscription-fsm.ts +1 -1
  58. package/src/drafts/draft07/varint.ts +4 -4
  59. package/src/drafts/draft14/codec.ts +339 -189
  60. package/src/drafts/draft14/index.ts +103 -108
  61. package/src/drafts/draft14/messages.ts +61 -61
  62. package/src/drafts/draft14/rules.ts +77 -34
  63. package/src/drafts/draft14/session-fsm.ts +458 -146
  64. package/src/drafts/draft14/session.ts +13 -13
  65. package/src/drafts/draft14/types.ts +68 -68
  66. package/src/index.ts +66 -31
  67. package/src/session.ts +20 -20
@@ -1,19 +1,24 @@
1
- import type { Draft14Message, Draft14MessageType } from './types.js';
2
1
  import type {
3
- SessionPhase,
4
- TransitionResult,
5
- ValidationResult,
2
+ AnnounceState,
3
+ FetchState,
6
4
  ProtocolViolation,
5
+ PublishState,
6
+ SessionPhase,
7
7
  SideEffect,
8
8
  SubscriptionState,
9
- AnnounceState,
10
- PublishState,
11
- FetchState,
12
- } from '../../core/session-types.js';
13
- import { getLegalOutgoing, getLegalIncoming, CLIENT_ONLY_MESSAGES, SERVER_ONLY_MESSAGES } from './rules.js';
9
+ TransitionResult,
10
+ ValidationResult,
11
+ } from "../../core/session-types.js";
12
+ import {
13
+ CLIENT_ONLY_MESSAGES,
14
+ getLegalIncoming,
15
+ getLegalOutgoing,
16
+ SERVER_ONLY_MESSAGES,
17
+ } from "./rules.js";
18
+ import type { Draft14Message, Draft14MessageType } from "./types.js";
14
19
 
15
20
  function violation(
16
- code: ProtocolViolation<Draft14MessageType>['code'],
21
+ code: ProtocolViolation<Draft14MessageType>["code"],
17
22
  message: string,
18
23
  currentPhase: SessionPhase,
19
24
  offendingMessage: Draft14MessageType,
@@ -22,23 +27,35 @@ function violation(
22
27
  }
23
28
 
24
29
  export class Draft14SessionFSM {
25
- private _phase: SessionPhase = 'idle';
26
- private _role: 'client' | 'server';
30
+ private _phase: SessionPhase = "idle";
31
+ private _role: "client" | "server";
27
32
  private _subscriptions = new Map<bigint, SubscriptionState>();
28
33
  private _publishes = new Map<bigint, PublishState>();
29
34
  private _fetches = new Map<bigint, FetchState>();
30
35
  private _requestIds = new Set<bigint>();
31
36
 
32
- constructor(role: 'client' | 'server') {
37
+ constructor(role: "client" | "server") {
33
38
  this._role = role;
34
39
  }
35
40
 
36
- get phase(): SessionPhase { return this._phase; }
37
- get role(): 'client' | 'server' { return this._role; }
38
- get subscriptions(): ReadonlyMap<bigint, SubscriptionState> { return this._subscriptions; }
39
- get announces(): ReadonlyMap<string, AnnounceState> { return new Map(); }
40
- get publishes(): ReadonlyMap<bigint, PublishState> { return this._publishes; }
41
- get fetches(): ReadonlyMap<bigint, FetchState> { return this._fetches; }
41
+ get phase(): SessionPhase {
42
+ return this._phase;
43
+ }
44
+ get role(): "client" | "server" {
45
+ return this._role;
46
+ }
47
+ get subscriptions(): ReadonlyMap<bigint, SubscriptionState> {
48
+ return this._subscriptions;
49
+ }
50
+ get announces(): ReadonlyMap<string, AnnounceState> {
51
+ return new Map();
52
+ }
53
+ get publishes(): ReadonlyMap<bigint, PublishState> {
54
+ return this._publishes;
55
+ }
56
+ get fetches(): ReadonlyMap<bigint, FetchState> {
57
+ return this._fetches;
58
+ }
42
59
 
43
60
  get legalOutgoing(): ReadonlySet<Draft14MessageType> {
44
61
  return getLegalOutgoing(this._phase, this._role);
@@ -49,41 +66,73 @@ export class Draft14SessionFSM {
49
66
  }
50
67
 
51
68
  // Validate role constraints
52
- private checkRole(message: Draft14Message, direction: 'inbound' | 'outbound'): ProtocolViolation<Draft14MessageType> | null {
53
- const senderRole = direction === 'outbound' ? this._role : (this._role === 'client' ? 'server' : 'client');
54
-
55
- if (CLIENT_ONLY_MESSAGES.has(message.type) && senderRole !== 'client') {
56
- return violation('ROLE_VIOLATION', `${message.type} can only be sent by client`, this._phase, message.type);
69
+ private checkRole(
70
+ message: Draft14Message,
71
+ direction: "inbound" | "outbound",
72
+ ): ProtocolViolation<Draft14MessageType> | null {
73
+ const senderRole =
74
+ direction === "outbound" ? this._role : this._role === "client" ? "server" : "client";
75
+
76
+ if (CLIENT_ONLY_MESSAGES.has(message.type) && senderRole !== "client") {
77
+ return violation(
78
+ "ROLE_VIOLATION",
79
+ `${message.type} can only be sent by client`,
80
+ this._phase,
81
+ message.type,
82
+ );
57
83
  }
58
- if (SERVER_ONLY_MESSAGES.has(message.type) && senderRole !== 'server') {
59
- return violation('ROLE_VIOLATION', `${message.type} can only be sent by server`, this._phase, message.type);
84
+ if (SERVER_ONLY_MESSAGES.has(message.type) && senderRole !== "server") {
85
+ return violation(
86
+ "ROLE_VIOLATION",
87
+ `${message.type} can only be sent by server`,
88
+ this._phase,
89
+ message.type,
90
+ );
60
91
  }
61
92
  return null;
62
93
  }
63
94
 
64
- private checkDuplicateRequestId(requestId: bigint, msgType: Draft14MessageType): ProtocolViolation<Draft14MessageType> | null {
95
+ private checkDuplicateRequestId(
96
+ requestId: bigint,
97
+ msgType: Draft14MessageType,
98
+ ): ProtocolViolation<Draft14MessageType> | null {
65
99
  if (this._requestIds.has(requestId)) {
66
- return violation('DUPLICATE_REQUEST_ID', `Request ID ${requestId} already in use`, this._phase, msgType);
100
+ return violation(
101
+ "DUPLICATE_REQUEST_ID",
102
+ `Request ID ${requestId} already in use`,
103
+ this._phase,
104
+ msgType,
105
+ );
67
106
  }
68
107
  return null;
69
108
  }
70
109
 
71
- private checkKnownRequestId(requestId: bigint, msgType: Draft14MessageType): ProtocolViolation<Draft14MessageType> | null {
110
+ private checkKnownRequestId(
111
+ requestId: bigint,
112
+ msgType: Draft14MessageType,
113
+ ): ProtocolViolation<Draft14MessageType> | null {
72
114
  if (!this._requestIds.has(requestId)) {
73
- return violation('UNKNOWN_REQUEST_ID', `No request with ID ${requestId}`, this._phase, msgType);
115
+ return violation(
116
+ "UNKNOWN_REQUEST_ID",
117
+ `No request with ID ${requestId}`,
118
+ this._phase,
119
+ msgType,
120
+ );
74
121
  }
75
122
  return null;
76
123
  }
77
124
 
78
125
  validateOutgoing(message: Draft14Message): ValidationResult<Draft14MessageType> {
79
- const roleViolation = this.checkRole(message, 'outbound');
126
+ const roleViolation = this.checkRole(message, "outbound");
80
127
  if (roleViolation) return { ok: false, violation: roleViolation };
81
128
 
82
129
  if (!this.legalOutgoing.has(message.type)) {
83
130
  return {
84
131
  ok: false,
85
132
  violation: violation(
86
- this._phase === 'idle' || this._phase === 'setup' ? 'MESSAGE_BEFORE_SETUP' : 'UNEXPECTED_MESSAGE',
133
+ this._phase === "idle" || this._phase === "setup"
134
+ ? "MESSAGE_BEFORE_SETUP"
135
+ : "UNEXPECTED_MESSAGE",
87
136
  `Cannot send ${message.type} in phase ${this._phase}`,
88
137
  this._phase,
89
138
  message.type,
@@ -94,60 +143,63 @@ export class Draft14SessionFSM {
94
143
  }
95
144
 
96
145
  receive(message: Draft14Message): TransitionResult<Draft14MessageType> {
97
- const roleViolation = this.checkRole(message, 'inbound');
146
+ const roleViolation = this.checkRole(message, "inbound");
98
147
  if (roleViolation) return { ok: false, violation: roleViolation };
99
148
 
100
- return this.applyTransition(message, 'inbound');
149
+ return this.applyTransition(message, "inbound");
101
150
  }
102
151
 
103
152
  send(message: Draft14Message): TransitionResult<Draft14MessageType> {
104
- const roleViolation = this.checkRole(message, 'outbound');
153
+ const roleViolation = this.checkRole(message, "outbound");
105
154
  if (roleViolation) return { ok: false, violation: roleViolation };
106
155
 
107
- return this.applyTransition(message, 'outbound');
156
+ return this.applyTransition(message, "outbound");
108
157
  }
109
158
 
110
- private applyTransition(message: Draft14Message, direction: 'inbound' | 'outbound'): TransitionResult<Draft14MessageType> {
159
+ private applyTransition(
160
+ message: Draft14Message,
161
+ direction: "inbound" | "outbound",
162
+ ): TransitionResult<Draft14MessageType> {
111
163
  const sideEffects: SideEffect[] = [];
112
164
 
113
165
  switch (message.type) {
114
- case 'client_setup':
166
+ case "client_setup":
115
167
  return this.handleClientSetup(message, direction);
116
- case 'server_setup':
168
+ case "server_setup":
117
169
  return this.handleServerSetup(message, direction);
118
- case 'goaway':
170
+ case "goaway":
119
171
  return this.handleGoAway(message, direction, sideEffects);
120
172
 
121
173
  // Subscribe lifecycle
122
- case 'subscribe':
174
+ case "subscribe":
123
175
  return this.handleSubscribe(message, direction, sideEffects);
124
- case 'subscribe_ok':
176
+ case "subscribe_ok":
125
177
  return this.handleSubscribeOk(message, direction, sideEffects);
126
- case 'subscribe_error':
178
+ case "subscribe_error":
127
179
  return this.handleSubscribeError(message, direction, sideEffects);
128
- case 'subscribe_update':
180
+ case "subscribe_update":
129
181
  return this.handleSubscribeUpdate(message, direction, sideEffects);
130
- case 'unsubscribe':
182
+ case "unsubscribe":
131
183
  return this.handleUnsubscribe(message, direction, sideEffects);
132
184
 
133
185
  // Publish lifecycle
134
- case 'publish':
186
+ case "publish":
135
187
  return this.handlePublish(message, direction, sideEffects);
136
- case 'publish_ok':
188
+ case "publish_ok":
137
189
  return this.handlePublishOk(message, direction, sideEffects);
138
- case 'publish_error':
190
+ case "publish_error":
139
191
  return this.handlePublishError(message, direction, sideEffects);
140
- case 'publish_done':
192
+ case "publish_done":
141
193
  return this.handlePublishDone(message, direction, sideEffects);
142
194
 
143
195
  // Fetch lifecycle
144
- case 'fetch':
196
+ case "fetch":
145
197
  return this.handleFetch(message, direction, sideEffects);
146
- case 'fetch_ok':
198
+ case "fetch_ok":
147
199
  return this.handleFetchOk(message, direction, sideEffects);
148
- case 'fetch_error':
200
+ case "fetch_error":
149
201
  return this.handleFetchError(message, direction, sideEffects);
150
- case 'fetch_cancel':
202
+ case "fetch_cancel":
151
203
  return this.handleFetchCancel(message, direction, sideEffects);
152
204
 
153
205
  // Publish namespace, subscribe namespace, track status, and other ready-phase messages
@@ -156,46 +208,98 @@ export class Draft14SessionFSM {
156
208
  }
157
209
  }
158
210
 
159
- private handleClientSetup(_message: Draft14Message, direction: 'inbound' | 'outbound'): TransitionResult<Draft14MessageType> {
160
- if (this._phase !== 'idle') {
161
- return { ok: false, violation: violation('SETUP_VIOLATION', 'CLIENT_SETUP already sent/received', this._phase, 'client_setup') };
211
+ private handleClientSetup(
212
+ _message: Draft14Message,
213
+ direction: "inbound" | "outbound",
214
+ ): TransitionResult<Draft14MessageType> {
215
+ if (this._phase !== "idle") {
216
+ return {
217
+ ok: false,
218
+ violation: violation(
219
+ "SETUP_VIOLATION",
220
+ "CLIENT_SETUP already sent/received",
221
+ this._phase,
222
+ "client_setup",
223
+ ),
224
+ };
162
225
  }
163
226
 
164
- if (direction === 'outbound' && this._role !== 'client') {
165
- return { ok: false, violation: violation('ROLE_VIOLATION', 'Only client can send CLIENT_SETUP', this._phase, 'client_setup') };
227
+ if (direction === "outbound" && this._role !== "client") {
228
+ return {
229
+ ok: false,
230
+ violation: violation(
231
+ "ROLE_VIOLATION",
232
+ "Only client can send CLIENT_SETUP",
233
+ this._phase,
234
+ "client_setup",
235
+ ),
236
+ };
166
237
  }
167
238
 
168
- this._phase = 'setup';
239
+ this._phase = "setup";
169
240
  return { ok: true, phase: this._phase, sideEffects: [] };
170
241
  }
171
242
 
172
- private handleServerSetup(_message: Draft14Message, direction: 'inbound' | 'outbound'): TransitionResult<Draft14MessageType> {
173
- if (this._phase !== 'setup') {
174
- return { ok: false, violation: violation('SETUP_VIOLATION', 'SERVER_SETUP before CLIENT_SETUP', this._phase, 'server_setup') };
243
+ private handleServerSetup(
244
+ _message: Draft14Message,
245
+ direction: "inbound" | "outbound",
246
+ ): TransitionResult<Draft14MessageType> {
247
+ if (this._phase !== "setup") {
248
+ return {
249
+ ok: false,
250
+ violation: violation(
251
+ "SETUP_VIOLATION",
252
+ "SERVER_SETUP before CLIENT_SETUP",
253
+ this._phase,
254
+ "server_setup",
255
+ ),
256
+ };
175
257
  }
176
258
 
177
- if (direction === 'outbound' && this._role !== 'server') {
178
- return { ok: false, violation: violation('ROLE_VIOLATION', 'Only server can send SERVER_SETUP', this._phase, 'server_setup') };
259
+ if (direction === "outbound" && this._role !== "server") {
260
+ return {
261
+ ok: false,
262
+ violation: violation(
263
+ "ROLE_VIOLATION",
264
+ "Only server can send SERVER_SETUP",
265
+ this._phase,
266
+ "server_setup",
267
+ ),
268
+ };
179
269
  }
180
270
 
181
- this._phase = 'ready';
182
- return { ok: true, phase: this._phase, sideEffects: [{ type: 'session-ready' }] };
271
+ this._phase = "ready";
272
+ return { ok: true, phase: this._phase, sideEffects: [{ type: "session-ready" }] };
183
273
  }
184
274
 
185
- private handleGoAway(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
186
- if (this._phase !== 'ready' && this._phase !== 'draining') {
187
- return { ok: false, violation: violation('UNEXPECTED_MESSAGE', `GOAWAY not valid in phase ${this._phase}`, this._phase, 'goaway') };
275
+ private handleGoAway(
276
+ message: Draft14Message,
277
+ _direction: "inbound" | "outbound",
278
+ sideEffects: SideEffect[],
279
+ ): TransitionResult<Draft14MessageType> {
280
+ if (this._phase !== "ready" && this._phase !== "draining") {
281
+ return {
282
+ ok: false,
283
+ violation: violation(
284
+ "UNEXPECTED_MESSAGE",
285
+ `GOAWAY not valid in phase ${this._phase}`,
286
+ this._phase,
287
+ "goaway",
288
+ ),
289
+ };
188
290
  }
189
- this._phase = 'draining';
190
- const goaway = message as import('./types.js').Draft14GoAway;
191
- sideEffects.push({ type: 'session-draining', goAwayUri: goaway.new_session_uri });
291
+ this._phase = "draining";
292
+ const goaway = message as import("./types.js").Draft14GoAway;
293
+ sideEffects.push({ type: "session-draining", goAwayUri: goaway.new_session_uri });
192
294
  return { ok: true, phase: this._phase, sideEffects };
193
295
  }
194
296
 
195
297
  private requireReady(msgType: Draft14MessageType): ProtocolViolation<Draft14MessageType> | null {
196
- if (this._phase !== 'ready' && this._phase !== 'draining') {
298
+ if (this._phase !== "ready" && this._phase !== "draining") {
197
299
  return violation(
198
- this._phase === 'idle' || this._phase === 'setup' ? 'MESSAGE_BEFORE_SETUP' : 'UNEXPECTED_MESSAGE',
300
+ this._phase === "idle" || this._phase === "setup"
301
+ ? "MESSAGE_BEFORE_SETUP"
302
+ : "UNEXPECTED_MESSAGE",
199
303
  `${msgType} requires ready phase, current: ${this._phase}`,
200
304
  this._phase,
201
305
  msgType,
@@ -206,18 +310,22 @@ export class Draft14SessionFSM {
206
310
 
207
311
  // ─── Subscribe lifecycle ──────────────────────────────────────────────────────
208
312
 
209
- private handleSubscribe(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
313
+ private handleSubscribe(
314
+ message: Draft14Message,
315
+ _direction: "inbound" | "outbound",
316
+ sideEffects: SideEffect[],
317
+ ): TransitionResult<Draft14MessageType> {
210
318
  const err = this.requireReady(message.type);
211
319
  if (err) return { ok: false, violation: err };
212
320
 
213
- const sub = message as import('./types.js').Draft14Subscribe;
321
+ const sub = message as import("./types.js").Draft14Subscribe;
214
322
  const dupErr = this.checkDuplicateRequestId(sub.request_id, message.type);
215
323
  if (dupErr) return { ok: false, violation: dupErr };
216
324
 
217
325
  this._requestIds.add(sub.request_id);
218
326
  this._subscriptions.set(sub.request_id, {
219
327
  subscribeId: sub.request_id,
220
- phase: 'pending',
328
+ phase: "pending",
221
329
  trackNamespace: sub.track_namespace,
222
330
  trackName: sub.track_name,
223
331
  });
@@ -225,240 +333,444 @@ export class Draft14SessionFSM {
225
333
  return { ok: true, phase: this._phase, sideEffects };
226
334
  }
227
335
 
228
- private handleSubscribeOk(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
336
+ private handleSubscribeOk(
337
+ message: Draft14Message,
338
+ _direction: "inbound" | "outbound",
339
+ sideEffects: SideEffect[],
340
+ ): TransitionResult<Draft14MessageType> {
229
341
  const err = this.requireReady(message.type);
230
342
  if (err) return { ok: false, violation: err };
231
343
 
232
- const ok = message as import('./types.js').Draft14SubscribeOk;
344
+ const ok = message as import("./types.js").Draft14SubscribeOk;
233
345
  const idErr = this.checkKnownRequestId(ok.request_id, message.type);
234
346
  if (idErr) return { ok: false, violation: idErr };
235
347
 
236
348
  const existing = this._subscriptions.get(ok.request_id);
237
349
  if (!existing) {
238
- return { ok: false, violation: violation('UNKNOWN_REQUEST_ID', `No subscription with request ID ${ok.request_id}`, this._phase, message.type) };
350
+ return {
351
+ ok: false,
352
+ violation: violation(
353
+ "UNKNOWN_REQUEST_ID",
354
+ `No subscription with request ID ${ok.request_id}`,
355
+ this._phase,
356
+ message.type,
357
+ ),
358
+ };
239
359
  }
240
- if (existing.phase !== 'pending') {
241
- return { ok: false, violation: violation('STATE_VIOLATION', `Subscription ${ok.request_id} is ${existing.phase}, not pending`, this._phase, message.type) };
360
+ if (existing.phase !== "pending") {
361
+ return {
362
+ ok: false,
363
+ violation: violation(
364
+ "STATE_VIOLATION",
365
+ `Subscription ${ok.request_id} is ${existing.phase}, not pending`,
366
+ this._phase,
367
+ message.type,
368
+ ),
369
+ };
242
370
  }
243
371
 
244
- this._subscriptions.set(ok.request_id, { ...existing, phase: 'active' });
245
- sideEffects.push({ type: 'subscription-activated', subscribeId: ok.request_id });
372
+ this._subscriptions.set(ok.request_id, { ...existing, phase: "active" });
373
+ sideEffects.push({ type: "subscription-activated", subscribeId: ok.request_id });
246
374
  return { ok: true, phase: this._phase, sideEffects };
247
375
  }
248
376
 
249
- private handleSubscribeError(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
377
+ private handleSubscribeError(
378
+ message: Draft14Message,
379
+ _direction: "inbound" | "outbound",
380
+ sideEffects: SideEffect[],
381
+ ): TransitionResult<Draft14MessageType> {
250
382
  const err = this.requireReady(message.type);
251
383
  if (err) return { ok: false, violation: err };
252
384
 
253
- const subErr = message as import('./types.js').Draft14SubscribeError;
385
+ const subErr = message as import("./types.js").Draft14SubscribeError;
254
386
  const idErr = this.checkKnownRequestId(subErr.request_id, message.type);
255
387
  if (idErr) return { ok: false, violation: idErr };
256
388
 
257
389
  const existing = this._subscriptions.get(subErr.request_id);
258
390
  if (!existing) {
259
- return { ok: false, violation: violation('UNKNOWN_REQUEST_ID', `No subscription with request ID ${subErr.request_id}`, this._phase, message.type) };
391
+ return {
392
+ ok: false,
393
+ violation: violation(
394
+ "UNKNOWN_REQUEST_ID",
395
+ `No subscription with request ID ${subErr.request_id}`,
396
+ this._phase,
397
+ message.type,
398
+ ),
399
+ };
260
400
  }
261
- if (existing.phase !== 'pending') {
262
- return { ok: false, violation: violation('STATE_VIOLATION', `Subscription ${subErr.request_id} is ${existing.phase}, not pending`, this._phase, message.type) };
401
+ if (existing.phase !== "pending") {
402
+ return {
403
+ ok: false,
404
+ violation: violation(
405
+ "STATE_VIOLATION",
406
+ `Subscription ${subErr.request_id} is ${existing.phase}, not pending`,
407
+ this._phase,
408
+ message.type,
409
+ ),
410
+ };
263
411
  }
264
412
 
265
- this._subscriptions.set(subErr.request_id, { ...existing, phase: 'error' });
266
- sideEffects.push({ type: 'subscription-ended', subscribeId: subErr.request_id, reason: subErr.reason_phrase });
413
+ this._subscriptions.set(subErr.request_id, { ...existing, phase: "error" });
414
+ sideEffects.push({
415
+ type: "subscription-ended",
416
+ subscribeId: subErr.request_id,
417
+ reason: subErr.reason_phrase,
418
+ });
267
419
  return { ok: true, phase: this._phase, sideEffects };
268
420
  }
269
421
 
270
- private handleSubscribeUpdate(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
422
+ private handleSubscribeUpdate(
423
+ message: Draft14Message,
424
+ _direction: "inbound" | "outbound",
425
+ sideEffects: SideEffect[],
426
+ ): TransitionResult<Draft14MessageType> {
271
427
  const err = this.requireReady(message.type);
272
428
  if (err) return { ok: false, violation: err };
273
429
 
274
- const update = message as import('./types.js').Draft14SubscribeUpdate;
430
+ const update = message as import("./types.js").Draft14SubscribeUpdate;
275
431
  const idErr = this.checkKnownRequestId(update.request_id, message.type);
276
432
  if (idErr) return { ok: false, violation: idErr };
277
433
 
278
434
  const existing = this._subscriptions.get(update.request_id);
279
435
  if (!existing) {
280
- return { ok: false, violation: violation('UNKNOWN_REQUEST_ID', `No subscription with request ID ${update.request_id}`, this._phase, message.type) };
436
+ return {
437
+ ok: false,
438
+ violation: violation(
439
+ "UNKNOWN_REQUEST_ID",
440
+ `No subscription with request ID ${update.request_id}`,
441
+ this._phase,
442
+ message.type,
443
+ ),
444
+ };
281
445
  }
282
- if (existing.phase !== 'active') {
283
- return { ok: false, violation: violation('STATE_VIOLATION', `Subscription ${update.request_id} is ${existing.phase}, not active`, this._phase, message.type) };
446
+ if (existing.phase !== "active") {
447
+ return {
448
+ ok: false,
449
+ violation: violation(
450
+ "STATE_VIOLATION",
451
+ `Subscription ${update.request_id} is ${existing.phase}, not active`,
452
+ this._phase,
453
+ message.type,
454
+ ),
455
+ };
284
456
  }
285
457
 
286
458
  return { ok: true, phase: this._phase, sideEffects };
287
459
  }
288
460
 
289
- private handleUnsubscribe(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
461
+ private handleUnsubscribe(
462
+ message: Draft14Message,
463
+ _direction: "inbound" | "outbound",
464
+ sideEffects: SideEffect[],
465
+ ): TransitionResult<Draft14MessageType> {
290
466
  const err = this.requireReady(message.type);
291
467
  if (err) return { ok: false, violation: err };
292
468
 
293
- const unsub = message as import('./types.js').Draft14Unsubscribe;
469
+ const unsub = message as import("./types.js").Draft14Unsubscribe;
294
470
  const idErr = this.checkKnownRequestId(unsub.request_id, message.type);
295
471
  if (idErr) return { ok: false, violation: idErr };
296
472
 
297
473
  const existing = this._subscriptions.get(unsub.request_id);
298
474
  if (!existing) {
299
- return { ok: false, violation: violation('UNKNOWN_REQUEST_ID', `No subscription with request ID ${unsub.request_id}`, this._phase, message.type) };
475
+ return {
476
+ ok: false,
477
+ violation: violation(
478
+ "UNKNOWN_REQUEST_ID",
479
+ `No subscription with request ID ${unsub.request_id}`,
480
+ this._phase,
481
+ message.type,
482
+ ),
483
+ };
300
484
  }
301
485
 
302
- this._subscriptions.set(unsub.request_id, { ...existing, phase: 'done' });
303
- sideEffects.push({ type: 'subscription-ended', subscribeId: unsub.request_id, reason: 'unsubscribed' });
486
+ this._subscriptions.set(unsub.request_id, { ...existing, phase: "done" });
487
+ sideEffects.push({
488
+ type: "subscription-ended",
489
+ subscribeId: unsub.request_id,
490
+ reason: "unsubscribed",
491
+ });
304
492
  return { ok: true, phase: this._phase, sideEffects };
305
493
  }
306
494
 
307
495
  // ─── Publish lifecycle ────────────────────────────────────────────────────────
308
496
 
309
- private handlePublish(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
497
+ private handlePublish(
498
+ message: Draft14Message,
499
+ _direction: "inbound" | "outbound",
500
+ sideEffects: SideEffect[],
501
+ ): TransitionResult<Draft14MessageType> {
310
502
  const err = this.requireReady(message.type);
311
503
  if (err) return { ok: false, violation: err };
312
504
 
313
- const pub = message as import('./types.js').Draft14Publish;
505
+ const pub = message as import("./types.js").Draft14Publish;
314
506
  const dupErr = this.checkDuplicateRequestId(pub.request_id, message.type);
315
507
  if (dupErr) return { ok: false, violation: dupErr };
316
508
 
317
509
  this._requestIds.add(pub.request_id);
318
510
  this._publishes.set(pub.request_id, {
319
511
  requestId: pub.request_id,
320
- phase: 'pending',
512
+ phase: "pending",
321
513
  });
322
514
 
323
515
  return { ok: true, phase: this._phase, sideEffects };
324
516
  }
325
517
 
326
- private handlePublishOk(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
518
+ private handlePublishOk(
519
+ message: Draft14Message,
520
+ _direction: "inbound" | "outbound",
521
+ sideEffects: SideEffect[],
522
+ ): TransitionResult<Draft14MessageType> {
327
523
  const err = this.requireReady(message.type);
328
524
  if (err) return { ok: false, violation: err };
329
525
 
330
- const ok = message as import('./types.js').Draft14PublishOk;
526
+ const ok = message as import("./types.js").Draft14PublishOk;
331
527
  const idErr = this.checkKnownRequestId(ok.request_id, message.type);
332
528
  if (idErr) return { ok: false, violation: idErr };
333
529
 
334
530
  const existing = this._publishes.get(ok.request_id);
335
531
  if (!existing) {
336
- return { ok: false, violation: violation('UNKNOWN_REQUEST_ID', `No publish with request ID ${ok.request_id}`, this._phase, message.type) };
532
+ return {
533
+ ok: false,
534
+ violation: violation(
535
+ "UNKNOWN_REQUEST_ID",
536
+ `No publish with request ID ${ok.request_id}`,
537
+ this._phase,
538
+ message.type,
539
+ ),
540
+ };
337
541
  }
338
- if (existing.phase !== 'pending') {
339
- return { ok: false, violation: violation('STATE_VIOLATION', `Publish ${ok.request_id} is ${existing.phase}, not pending`, this._phase, message.type) };
542
+ if (existing.phase !== "pending") {
543
+ return {
544
+ ok: false,
545
+ violation: violation(
546
+ "STATE_VIOLATION",
547
+ `Publish ${ok.request_id} is ${existing.phase}, not pending`,
548
+ this._phase,
549
+ message.type,
550
+ ),
551
+ };
340
552
  }
341
553
 
342
- this._publishes.set(ok.request_id, { ...existing, phase: 'active' });
343
- sideEffects.push({ type: 'publish-activated', requestId: ok.request_id });
554
+ this._publishes.set(ok.request_id, { ...existing, phase: "active" });
555
+ sideEffects.push({ type: "publish-activated", requestId: ok.request_id });
344
556
  return { ok: true, phase: this._phase, sideEffects };
345
557
  }
346
558
 
347
- private handlePublishError(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
559
+ private handlePublishError(
560
+ message: Draft14Message,
561
+ _direction: "inbound" | "outbound",
562
+ sideEffects: SideEffect[],
563
+ ): TransitionResult<Draft14MessageType> {
348
564
  const err = this.requireReady(message.type);
349
565
  if (err) return { ok: false, violation: err };
350
566
 
351
- const pubErr = message as import('./types.js').Draft14PublishError;
567
+ const pubErr = message as import("./types.js").Draft14PublishError;
352
568
  const idErr = this.checkKnownRequestId(pubErr.request_id, message.type);
353
569
  if (idErr) return { ok: false, violation: idErr };
354
570
 
355
571
  const existing = this._publishes.get(pubErr.request_id);
356
572
  if (!existing) {
357
- return { ok: false, violation: violation('UNKNOWN_REQUEST_ID', `No publish with request ID ${pubErr.request_id}`, this._phase, message.type) };
573
+ return {
574
+ ok: false,
575
+ violation: violation(
576
+ "UNKNOWN_REQUEST_ID",
577
+ `No publish with request ID ${pubErr.request_id}`,
578
+ this._phase,
579
+ message.type,
580
+ ),
581
+ };
358
582
  }
359
- if (existing.phase !== 'pending') {
360
- return { ok: false, violation: violation('STATE_VIOLATION', `Publish ${pubErr.request_id} is ${existing.phase}, not pending`, this._phase, message.type) };
583
+ if (existing.phase !== "pending") {
584
+ return {
585
+ ok: false,
586
+ violation: violation(
587
+ "STATE_VIOLATION",
588
+ `Publish ${pubErr.request_id} is ${existing.phase}, not pending`,
589
+ this._phase,
590
+ message.type,
591
+ ),
592
+ };
361
593
  }
362
594
 
363
- this._publishes.set(pubErr.request_id, { ...existing, phase: 'error' });
364
- sideEffects.push({ type: 'publish-ended', requestId: pubErr.request_id, reason: pubErr.reason_phrase });
595
+ this._publishes.set(pubErr.request_id, { ...existing, phase: "error" });
596
+ sideEffects.push({
597
+ type: "publish-ended",
598
+ requestId: pubErr.request_id,
599
+ reason: pubErr.reason_phrase,
600
+ });
365
601
  return { ok: true, phase: this._phase, sideEffects };
366
602
  }
367
603
 
368
- private handlePublishDone(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
604
+ private handlePublishDone(
605
+ message: Draft14Message,
606
+ _direction: "inbound" | "outbound",
607
+ sideEffects: SideEffect[],
608
+ ): TransitionResult<Draft14MessageType> {
369
609
  const err = this.requireReady(message.type);
370
610
  if (err) return { ok: false, violation: err };
371
611
 
372
- const done = message as import('./types.js').Draft14PublishDone;
612
+ const done = message as import("./types.js").Draft14PublishDone;
373
613
  const idErr = this.checkKnownRequestId(done.request_id, message.type);
374
614
  if (idErr) return { ok: false, violation: idErr };
375
615
 
376
616
  const existing = this._publishes.get(done.request_id);
377
617
  if (!existing) {
378
- return { ok: false, violation: violation('UNKNOWN_REQUEST_ID', `No publish with request ID ${done.request_id}`, this._phase, message.type) };
618
+ return {
619
+ ok: false,
620
+ violation: violation(
621
+ "UNKNOWN_REQUEST_ID",
622
+ `No publish with request ID ${done.request_id}`,
623
+ this._phase,
624
+ message.type,
625
+ ),
626
+ };
379
627
  }
380
628
 
381
- this._publishes.set(done.request_id, { ...existing, phase: 'done' });
382
- sideEffects.push({ type: 'publish-ended', requestId: done.request_id, reason: done.reason_phrase });
629
+ this._publishes.set(done.request_id, { ...existing, phase: "done" });
630
+ sideEffects.push({
631
+ type: "publish-ended",
632
+ requestId: done.request_id,
633
+ reason: done.reason_phrase,
634
+ });
383
635
  return { ok: true, phase: this._phase, sideEffects };
384
636
  }
385
637
 
386
638
  // ─── Fetch lifecycle ──────────────────────────────────────────────────────────
387
639
 
388
- private handleFetch(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
640
+ private handleFetch(
641
+ message: Draft14Message,
642
+ _direction: "inbound" | "outbound",
643
+ sideEffects: SideEffect[],
644
+ ): TransitionResult<Draft14MessageType> {
389
645
  const err = this.requireReady(message.type);
390
646
  if (err) return { ok: false, violation: err };
391
647
 
392
- const fetch = message as import('./types.js').Draft14Fetch;
648
+ const fetch = message as import("./types.js").Draft14Fetch;
393
649
  const dupErr = this.checkDuplicateRequestId(fetch.request_id, message.type);
394
650
  if (dupErr) return { ok: false, violation: dupErr };
395
651
 
396
652
  this._requestIds.add(fetch.request_id);
397
653
  this._fetches.set(fetch.request_id, {
398
654
  requestId: fetch.request_id,
399
- phase: 'pending',
655
+ phase: "pending",
400
656
  });
401
657
 
402
658
  return { ok: true, phase: this._phase, sideEffects };
403
659
  }
404
660
 
405
- private handleFetchOk(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
661
+ private handleFetchOk(
662
+ message: Draft14Message,
663
+ _direction: "inbound" | "outbound",
664
+ sideEffects: SideEffect[],
665
+ ): TransitionResult<Draft14MessageType> {
406
666
  const err = this.requireReady(message.type);
407
667
  if (err) return { ok: false, violation: err };
408
668
 
409
- const ok = message as import('./types.js').Draft14FetchOk;
669
+ const ok = message as import("./types.js").Draft14FetchOk;
410
670
  const idErr = this.checkKnownRequestId(ok.request_id, message.type);
411
671
  if (idErr) return { ok: false, violation: idErr };
412
672
 
413
673
  const existing = this._fetches.get(ok.request_id);
414
674
  if (!existing) {
415
- return { ok: false, violation: violation('UNKNOWN_REQUEST_ID', `No fetch with request ID ${ok.request_id}`, this._phase, message.type) };
675
+ return {
676
+ ok: false,
677
+ violation: violation(
678
+ "UNKNOWN_REQUEST_ID",
679
+ `No fetch with request ID ${ok.request_id}`,
680
+ this._phase,
681
+ message.type,
682
+ ),
683
+ };
416
684
  }
417
- if (existing.phase !== 'pending') {
418
- return { ok: false, violation: violation('STATE_VIOLATION', `Fetch ${ok.request_id} is ${existing.phase}, not pending`, this._phase, message.type) };
685
+ if (existing.phase !== "pending") {
686
+ return {
687
+ ok: false,
688
+ violation: violation(
689
+ "STATE_VIOLATION",
690
+ `Fetch ${ok.request_id} is ${existing.phase}, not pending`,
691
+ this._phase,
692
+ message.type,
693
+ ),
694
+ };
419
695
  }
420
696
 
421
- this._fetches.set(ok.request_id, { ...existing, phase: 'active' });
422
- sideEffects.push({ type: 'fetch-activated', requestId: ok.request_id });
697
+ this._fetches.set(ok.request_id, { ...existing, phase: "active" });
698
+ sideEffects.push({ type: "fetch-activated", requestId: ok.request_id });
423
699
  return { ok: true, phase: this._phase, sideEffects };
424
700
  }
425
701
 
426
- private handleFetchError(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
702
+ private handleFetchError(
703
+ message: Draft14Message,
704
+ _direction: "inbound" | "outbound",
705
+ sideEffects: SideEffect[],
706
+ ): TransitionResult<Draft14MessageType> {
427
707
  const err = this.requireReady(message.type);
428
708
  if (err) return { ok: false, violation: err };
429
709
 
430
- const fetchErr = message as import('./types.js').Draft14FetchError;
710
+ const fetchErr = message as import("./types.js").Draft14FetchError;
431
711
  const idErr = this.checkKnownRequestId(fetchErr.request_id, message.type);
432
712
  if (idErr) return { ok: false, violation: idErr };
433
713
 
434
714
  const existing = this._fetches.get(fetchErr.request_id);
435
715
  if (!existing) {
436
- return { ok: false, violation: violation('UNKNOWN_REQUEST_ID', `No fetch with request ID ${fetchErr.request_id}`, this._phase, message.type) };
716
+ return {
717
+ ok: false,
718
+ violation: violation(
719
+ "UNKNOWN_REQUEST_ID",
720
+ `No fetch with request ID ${fetchErr.request_id}`,
721
+ this._phase,
722
+ message.type,
723
+ ),
724
+ };
437
725
  }
438
- if (existing.phase !== 'pending') {
439
- return { ok: false, violation: violation('STATE_VIOLATION', `Fetch ${fetchErr.request_id} is ${existing.phase}, not pending`, this._phase, message.type) };
726
+ if (existing.phase !== "pending") {
727
+ return {
728
+ ok: false,
729
+ violation: violation(
730
+ "STATE_VIOLATION",
731
+ `Fetch ${fetchErr.request_id} is ${existing.phase}, not pending`,
732
+ this._phase,
733
+ message.type,
734
+ ),
735
+ };
440
736
  }
441
737
 
442
- this._fetches.set(fetchErr.request_id, { ...existing, phase: 'error' });
443
- sideEffects.push({ type: 'fetch-ended', requestId: fetchErr.request_id, reason: fetchErr.reason_phrase });
738
+ this._fetches.set(fetchErr.request_id, { ...existing, phase: "error" });
739
+ sideEffects.push({
740
+ type: "fetch-ended",
741
+ requestId: fetchErr.request_id,
742
+ reason: fetchErr.reason_phrase,
743
+ });
444
744
  return { ok: true, phase: this._phase, sideEffects };
445
745
  }
446
746
 
447
- private handleFetchCancel(message: Draft14Message, _direction: 'inbound' | 'outbound', sideEffects: SideEffect[]): TransitionResult<Draft14MessageType> {
747
+ private handleFetchCancel(
748
+ message: Draft14Message,
749
+ _direction: "inbound" | "outbound",
750
+ sideEffects: SideEffect[],
751
+ ): TransitionResult<Draft14MessageType> {
448
752
  const err = this.requireReady(message.type);
449
753
  if (err) return { ok: false, violation: err };
450
754
 
451
- const cancel = message as import('./types.js').Draft14FetchCancel;
755
+ const cancel = message as import("./types.js").Draft14FetchCancel;
452
756
  const idErr = this.checkKnownRequestId(cancel.request_id, message.type);
453
757
  if (idErr) return { ok: false, violation: idErr };
454
758
 
455
759
  const existing = this._fetches.get(cancel.request_id);
456
760
  if (!existing) {
457
- return { ok: false, violation: violation('UNKNOWN_REQUEST_ID', `No fetch with request ID ${cancel.request_id}`, this._phase, message.type) };
761
+ return {
762
+ ok: false,
763
+ violation: violation(
764
+ "UNKNOWN_REQUEST_ID",
765
+ `No fetch with request ID ${cancel.request_id}`,
766
+ this._phase,
767
+ message.type,
768
+ ),
769
+ };
458
770
  }
459
771
 
460
- this._fetches.set(cancel.request_id, { ...existing, phase: 'cancelled' });
461
- sideEffects.push({ type: 'fetch-ended', requestId: cancel.request_id, reason: 'cancelled' });
772
+ this._fetches.set(cancel.request_id, { ...existing, phase: "cancelled" });
773
+ sideEffects.push({ type: "fetch-ended", requestId: cancel.request_id, reason: "cancelled" });
462
774
  return { ok: true, phase: this._phase, sideEffects };
463
775
  }
464
776
 
@@ -471,7 +783,7 @@ export class Draft14SessionFSM {
471
783
  }
472
784
 
473
785
  reset(): void {
474
- this._phase = 'idle';
786
+ this._phase = "idle";
475
787
  this._subscriptions.clear();
476
788
  this._publishes.clear();
477
789
  this._fetches.clear();