@moqtap/codec 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -95
- package/dist/chunk-4RIXXEII.js +1275 -0
- package/dist/chunk-4XYGE53S.cjs +698 -0
- package/dist/chunk-4YJANAXU.cjs +1109 -0
- package/dist/chunk-6AEHWULA.cjs +641 -0
- package/dist/chunk-7DUBLRXC.js +680 -0
- package/dist/{chunk-WNTXF3DE.cjs → chunk-7IVGHMKJ.cjs} +164 -62
- package/dist/{chunk-YBSEOSSP.js → chunk-A27S7HW7.js} +5 -1
- package/dist/chunk-BISI45MN.cjs +680 -0
- package/dist/{chunk-3BSZ55L3.cjs → chunk-BPNL5YFQ.cjs} +158 -24
- package/dist/chunk-CXDHOMHG.js +1097 -0
- package/dist/chunk-DUBCL3WF.cjs +749 -0
- package/dist/chunk-DWK5ZQZ4.js +642 -0
- package/dist/chunk-E6E3NQYU.js +680 -0
- package/dist/chunk-EFM5T7OM.js +698 -0
- package/dist/chunk-ENURAVHI.cjs +680 -0
- package/dist/{chunk-5WFXFLL4.cjs → chunk-FUFTMAQD.cjs} +96 -63
- package/dist/{chunk-2NARXGVA.cjs → chunk-FWISIR26.cjs} +5 -1
- package/dist/{chunk-YPXLV5YK.js → chunk-FXZ2MYKJ.js} +376 -38
- package/dist/chunk-G26SJ6XS.cjs +1341 -0
- package/dist/chunk-G7GI7LJA.js +737 -0
- package/dist/chunk-GXEW4COZ.cjs +737 -0
- package/dist/chunk-HSVYF6XX.cjs +1361 -0
- package/dist/chunk-IBVM4DMJ.cjs +1097 -0
- package/dist/chunk-IV2H5CFI.cjs +1275 -0
- package/dist/chunk-IV2HRJVT.js +1198 -0
- package/dist/chunk-JSQM2MG3.js +680 -0
- package/dist/chunk-K4OLITS2.cjs +1055 -0
- package/dist/{chunk-UOBWHJA5.js → chunk-KFTCU2P6.js} +2 -3
- package/dist/chunk-LH4NTURO.js +1361 -0
- package/dist/{chunk-DC4L6ZIT.js → chunk-MFAP7R6L.js} +154 -20
- package/dist/chunk-NGVE2RZT.js +1097 -0
- package/dist/chunk-NUX5BHWO.js +1341 -0
- package/dist/chunk-PJRA2TQ5.js +1055 -0
- package/dist/chunk-RVJAGE4S.cjs +1198 -0
- package/dist/{chunk-QYG6KGOV.cjs → chunk-RWQ43Z4F.cjs} +2 -3
- package/dist/chunk-RZHAPEXO.js +749 -0
- package/dist/chunk-ST24APEO.js +1109 -0
- package/dist/chunk-SYHW3FLI.cjs +642 -0
- package/dist/chunk-TLYNOOQP.cjs +432 -0
- package/dist/{chunk-23YG7F46.js → chunk-TMNGRIPL.js} +153 -51
- package/dist/{chunk-IQPDRQVC.js → chunk-U2B3B42P.js} +62 -29
- package/dist/chunk-UNS34PTA.cjs +680 -0
- package/dist/chunk-UR6JKS56.js +432 -0
- package/dist/{chunk-GDRGWFEK.cjs → chunk-UYXTY6ZQ.cjs} +376 -38
- package/dist/chunk-XUUCOLWU.cjs +1097 -0
- package/dist/chunk-YG5KJESI.js +641 -0
- package/dist/chunk-ZBKE2QRQ.js +1401 -0
- package/dist/chunk-ZSPO2GF2.cjs +1401 -0
- package/dist/codec-95k8CAu5.d.cts +35 -0
- package/dist/codec-AFuOxfsO.d.ts +60 -0
- package/dist/codec-B-UJ5Iow.d.cts +75 -0
- package/dist/codec-BC5jfvMb.d.ts +35 -0
- package/dist/codec-BECYPfY8.d.ts +35 -0
- package/dist/codec-BsPU1vNC.d.ts +39 -0
- package/dist/codec-BvpuF-6u.d.cts +39 -0
- package/dist/codec-C8jZI5Cx.d.cts +39 -0
- package/dist/codec-CAevkgf5.d.cts +33 -0
- package/dist/codec-CSUqCrRs.d.ts +39 -0
- package/dist/codec-C_HMXNK_.d.ts +33 -0
- package/dist/{codec-CTvFtQQI.d.cts → codec-CpuvYTSV.d.cts} +5 -5
- package/dist/codec-D0x8-SCw.d.cts +35 -0
- package/dist/codec-D7ARhpG1.d.ts +75 -0
- package/dist/codec-DNAUGshO.d.cts +60 -0
- package/dist/codec-DPx_QNn0.d.ts +31 -0
- package/dist/{codec-qPzfmLNu.d.ts → codec-DRhCx_hw.d.ts} +5 -5
- package/dist/codec-Db7YPe3l.d.ts +31 -0
- package/dist/codec-axkJpb7D.d.cts +31 -0
- package/dist/codec-ujAbFaep.d.cts +31 -0
- package/dist/draft10-session.cjs +6 -0
- package/dist/draft10-session.d.cts +8 -0
- package/dist/draft10-session.d.ts +8 -0
- package/dist/draft10-session.js +6 -0
- package/dist/draft10.cjs +115 -0
- package/dist/draft10.d.cts +95 -0
- package/dist/draft10.d.ts +95 -0
- package/dist/draft10.js +115 -0
- package/dist/draft11-session.cjs +6 -0
- package/dist/draft11-session.d.cts +8 -0
- package/dist/draft11-session.d.ts +8 -0
- package/dist/draft11-session.js +6 -0
- package/dist/draft11.cjs +109 -0
- package/dist/draft11.d.cts +99 -0
- package/dist/draft11.d.ts +99 -0
- package/dist/draft11.js +109 -0
- package/dist/draft12-session.cjs +6 -0
- package/dist/draft12-session.d.cts +8 -0
- package/dist/draft12-session.d.ts +8 -0
- package/dist/draft12-session.js +6 -0
- package/dist/draft12.cjs +117 -0
- package/dist/draft12.d.cts +106 -0
- package/dist/draft12.d.ts +106 -0
- package/dist/draft12.js +117 -0
- package/dist/draft13-session.cjs +6 -0
- package/dist/draft13-session.d.cts +8 -0
- package/dist/draft13-session.d.ts +8 -0
- package/dist/draft13-session.js +6 -0
- package/dist/draft13.cjs +119 -0
- package/dist/draft13.d.cts +108 -0
- package/dist/draft13.d.ts +108 -0
- package/dist/draft13.js +119 -0
- package/dist/draft14-session.cjs +2 -2
- package/dist/draft14-session.d.cts +4 -4
- package/dist/draft14-session.d.ts +4 -4
- package/dist/draft14-session.js +1 -1
- package/dist/draft14.cjs +4 -4
- package/dist/draft14.d.cts +27 -15
- package/dist/draft14.d.ts +27 -15
- package/dist/draft14.js +3 -3
- package/dist/draft15-session.cjs +6 -0
- package/dist/draft15-session.d.cts +8 -0
- package/dist/draft15-session.d.ts +8 -0
- package/dist/draft15-session.js +6 -0
- package/dist/draft15.cjs +111 -0
- package/dist/draft15.d.cts +93 -0
- package/dist/draft15.d.ts +93 -0
- package/dist/draft15.js +111 -0
- package/dist/draft16-session.cjs +6 -0
- package/dist/draft16-session.d.cts +8 -0
- package/dist/draft16-session.d.ts +8 -0
- package/dist/draft16-session.js +6 -0
- package/dist/draft16.cjs +113 -0
- package/dist/draft16.d.cts +94 -0
- package/dist/draft16.d.ts +94 -0
- package/dist/draft16.js +113 -0
- package/dist/draft17-session.cjs +8 -0
- package/dist/draft17-session.d.cts +51 -0
- package/dist/draft17-session.d.ts +51 -0
- package/dist/draft17-session.js +8 -0
- package/dist/draft17.cjs +99 -0
- package/dist/draft17.d.cts +40 -0
- package/dist/draft17.d.ts +40 -0
- package/dist/draft17.js +99 -0
- package/dist/draft7-session.cjs +3 -3
- package/dist/draft7-session.d.cts +3 -3
- package/dist/draft7-session.d.ts +3 -3
- package/dist/draft7-session.js +2 -2
- package/dist/draft7.cjs +6 -6
- package/dist/draft7.d.cts +10 -10
- package/dist/draft7.d.ts +10 -10
- package/dist/draft7.js +3 -3
- package/dist/draft8-session.cjs +6 -0
- package/dist/draft8-session.d.cts +8 -0
- package/dist/draft8-session.d.ts +8 -0
- package/dist/draft8-session.js +6 -0
- package/dist/draft8.cjs +115 -0
- package/dist/draft8.d.cts +95 -0
- package/dist/draft8.d.ts +95 -0
- package/dist/draft8.js +115 -0
- package/dist/draft9-session.cjs +6 -0
- package/dist/draft9-session.d.cts +8 -0
- package/dist/draft9-session.d.ts +8 -0
- package/dist/draft9-session.js +6 -0
- package/dist/draft9.cjs +115 -0
- package/dist/draft9.d.cts +95 -0
- package/dist/draft9.d.ts +95 -0
- package/dist/draft9.js +115 -0
- package/dist/index.cjs +79 -7
- package/dist/index.d.cts +71 -8
- package/dist/index.d.ts +71 -8
- package/dist/index.js +77 -5
- package/dist/{session-types-B9NIf7_F.d.ts → session-types-CJIFbTPd.d.ts} +20 -20
- package/dist/{session-types-CCo-oA-d.d.cts → session-types-Cbq8IGCP.d.cts} +20 -20
- package/dist/session.cjs +5 -5
- package/dist/session.d.cts +3 -3
- package/dist/session.d.ts +3 -3
- package/dist/session.js +5 -5
- package/dist/types-4VxSL2Ho.d.cts +261 -0
- package/dist/types-4VxSL2Ho.d.ts +261 -0
- package/dist/types-B2afJZM-.d.cts +236 -0
- package/dist/types-B2afJZM-.d.ts +236 -0
- package/dist/{types-CIk5W10V.d.ts → types-BTFeKYCb.d.cts} +37 -37
- package/dist/{types-CIk5W10V.d.cts → types-BTFeKYCb.d.ts} +37 -37
- package/dist/types-Bg6QYNVt.d.cts +290 -0
- package/dist/types-Bg6QYNVt.d.ts +290 -0
- package/dist/types-C_1HrqBl.d.cts +306 -0
- package/dist/types-C_1HrqBl.d.ts +306 -0
- package/dist/types-Cw4WE9dh.d.cts +261 -0
- package/dist/types-Cw4WE9dh.d.ts +261 -0
- package/dist/types-D5gNQiDj.d.cts +261 -0
- package/dist/types-D5gNQiDj.d.ts +261 -0
- package/dist/types-DqCDFqgB.d.cts +230 -0
- package/dist/types-DqCDFqgB.d.ts +230 -0
- package/dist/types-ERexTpT8.d.cts +217 -0
- package/dist/types-ERexTpT8.d.ts +217 -0
- package/dist/{types-ClXELFGN.d.cts → types-QNXoxC9Y.d.cts} +36 -41
- package/dist/{types-ClXELFGN.d.ts → types-QNXoxC9Y.d.ts} +36 -41
- package/dist/types-r-CasCf1.d.cts +262 -0
- package/dist/types-r-CasCf1.d.ts +262 -0
- package/package.json +116 -8
- package/src/core/buffer-reader.ts +16 -9
- package/src/core/buffer-writer.ts +2 -2
- package/src/core/errors.ts +1 -1
- package/src/core/session-types.ts +28 -41
- package/src/core/types.ts +92 -75
- package/src/drafts/draft07/announce-fsm.ts +1 -1
- package/src/drafts/draft07/codec.ts +235 -118
- package/src/drafts/draft07/index.ts +43 -44
- package/src/drafts/draft07/messages.ts +1 -1
- package/src/drafts/draft07/parameters.ts +2 -2
- package/src/drafts/draft07/rules.ts +67 -38
- package/src/drafts/draft07/session-fsm.ts +330 -117
- package/src/drafts/draft07/session.ts +10 -10
- package/src/drafts/draft07/subscription-fsm.ts +1 -1
- package/src/drafts/draft07/varint.ts +4 -4
- package/src/drafts/draft08/codec.ts +1254 -0
- package/src/drafts/draft08/index.ts +125 -0
- package/src/drafts/draft08/messages.ts +72 -0
- package/src/drafts/draft08/rules.ts +91 -0
- package/src/drafts/draft08/session-fsm.ts +718 -0
- package/src/drafts/draft08/session.ts +26 -0
- package/src/drafts/draft08/types.ts +377 -0
- package/src/drafts/draft09/codec.ts +1235 -0
- package/src/drafts/draft09/index.ts +125 -0
- package/src/drafts/draft09/messages.ts +72 -0
- package/src/drafts/draft09/rules.ts +91 -0
- package/src/drafts/draft09/session-fsm.ts +718 -0
- package/src/drafts/draft09/session.ts +26 -0
- package/src/drafts/draft09/types.ts +376 -0
- package/src/drafts/draft10/codec.ts +1235 -0
- package/src/drafts/draft10/index.ts +125 -0
- package/src/drafts/draft10/messages.ts +72 -0
- package/src/drafts/draft10/rules.ts +91 -0
- package/src/drafts/draft10/session-fsm.ts +718 -0
- package/src/drafts/draft10/session.ts +26 -0
- package/src/drafts/draft10/types.ts +376 -0
- package/src/drafts/draft11/codec.ts +1198 -0
- package/src/drafts/draft11/index.ts +123 -0
- package/src/drafts/draft11/messages.ts +71 -0
- package/src/drafts/draft11/rules.ts +100 -0
- package/src/drafts/draft11/session-fsm.ts +758 -0
- package/src/drafts/draft11/session.ts +26 -0
- package/src/drafts/draft11/types.ts +375 -0
- package/src/drafts/draft12/codec.ts +1354 -0
- package/src/drafts/draft12/index.ts +130 -0
- package/src/drafts/draft12/messages.ts +84 -0
- package/src/drafts/draft12/rules.ts +106 -0
- package/src/drafts/draft12/session-fsm.ts +805 -0
- package/src/drafts/draft12/session.ts +26 -0
- package/src/drafts/draft12/types.ts +414 -0
- package/src/drafts/draft13/codec.ts +1438 -0
- package/src/drafts/draft13/index.ts +132 -0
- package/src/drafts/draft13/messages.ts +86 -0
- package/src/drafts/draft13/rules.ts +108 -0
- package/src/drafts/draft13/session-fsm.ts +819 -0
- package/src/drafts/draft13/session.ts +26 -0
- package/src/drafts/draft13/types.ts +433 -0
- package/src/drafts/draft14/codec.ts +339 -189
- package/src/drafts/draft14/index.ts +103 -108
- package/src/drafts/draft14/messages.ts +61 -61
- package/src/drafts/draft14/rules.ts +77 -34
- package/src/drafts/draft14/session-fsm.ts +640 -147
- package/src/drafts/draft14/session.ts +13 -13
- package/src/drafts/draft14/types.ts +68 -68
- package/src/drafts/draft15/codec.ts +1661 -0
- package/src/drafts/draft15/index.ts +121 -0
- package/src/drafts/draft15/messages.ts +64 -0
- package/src/drafts/draft15/rules.ts +95 -0
- package/src/drafts/draft15/session-fsm.ts +687 -0
- package/src/drafts/draft15/session.ts +26 -0
- package/src/drafts/draft15/types.ts +336 -0
- package/src/drafts/draft16/codec.ts +1623 -0
- package/src/drafts/draft16/index.ts +123 -0
- package/src/drafts/draft16/messages.ts +67 -0
- package/src/drafts/draft16/rules.ts +96 -0
- package/src/drafts/draft16/session-fsm.ts +682 -0
- package/src/drafts/draft16/session.ts +26 -0
- package/src/drafts/draft16/types.ts +354 -0
- package/src/drafts/draft17/codec.ts +1621 -0
- package/src/drafts/draft17/index.ts +105 -0
- package/src/drafts/draft17/messages.ts +53 -0
- package/src/drafts/draft17/rules.ts +85 -0
- package/src/drafts/draft17/session-fsm.ts +437 -0
- package/src/drafts/draft17/session.ts +15 -0
- package/src/drafts/draft17/types.ts +310 -0
- package/src/index.ts +283 -33
- package/src/session.ts +20 -20
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
import type { Draft14Message, Draft14MessageType } from './types.js';
|
|
2
1
|
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
ValidationResult,
|
|
2
|
+
AnnounceState,
|
|
3
|
+
FetchState,
|
|
6
4
|
ProtocolViolation,
|
|
5
|
+
PublishState,
|
|
6
|
+
SessionPhase,
|
|
7
7
|
SideEffect,
|
|
8
8
|
SubscriptionState,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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>[
|
|
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 =
|
|
26
|
-
private _role:
|
|
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:
|
|
37
|
+
constructor(role: "client" | "server") {
|
|
33
38
|
this._role = role;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
get phase(): SessionPhase {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
get
|
|
40
|
-
|
|
41
|
-
|
|
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(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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 !==
|
|
59
|
-
return violation(
|
|
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(
|
|
95
|
+
private checkDuplicateRequestId(
|
|
96
|
+
requestId: bigint,
|
|
97
|
+
msgType: Draft14MessageType,
|
|
98
|
+
): ProtocolViolation<Draft14MessageType> | null {
|
|
65
99
|
if (this._requestIds.has(requestId)) {
|
|
66
|
-
return violation(
|
|
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(
|
|
110
|
+
private checkKnownRequestId(
|
|
111
|
+
requestId: bigint,
|
|
112
|
+
msgType: Draft14MessageType,
|
|
113
|
+
): ProtocolViolation<Draft14MessageType> | null {
|
|
72
114
|
if (!this._requestIds.has(requestId)) {
|
|
73
|
-
return violation(
|
|
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,
|
|
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 ===
|
|
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,108 +143,193 @@ export class Draft14SessionFSM {
|
|
|
94
143
|
}
|
|
95
144
|
|
|
96
145
|
receive(message: Draft14Message): TransitionResult<Draft14MessageType> {
|
|
97
|
-
const roleViolation = this.checkRole(message,
|
|
146
|
+
const roleViolation = this.checkRole(message, "inbound");
|
|
98
147
|
if (roleViolation) return { ok: false, violation: roleViolation };
|
|
99
148
|
|
|
100
|
-
return this.applyTransition(message,
|
|
149
|
+
return this.applyTransition(message, "inbound");
|
|
101
150
|
}
|
|
102
151
|
|
|
103
152
|
send(message: Draft14Message): TransitionResult<Draft14MessageType> {
|
|
104
|
-
const roleViolation = this.checkRole(message,
|
|
153
|
+
const roleViolation = this.checkRole(message, "outbound");
|
|
105
154
|
if (roleViolation) return { ok: false, violation: roleViolation };
|
|
106
155
|
|
|
107
|
-
return this.applyTransition(message,
|
|
156
|
+
return this.applyTransition(message, "outbound");
|
|
108
157
|
}
|
|
109
158
|
|
|
110
|
-
private applyTransition(
|
|
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
|
|
166
|
+
case "client_setup":
|
|
115
167
|
return this.handleClientSetup(message, direction);
|
|
116
|
-
case
|
|
168
|
+
case "server_setup":
|
|
117
169
|
return this.handleServerSetup(message, direction);
|
|
118
|
-
case
|
|
170
|
+
case "goaway":
|
|
119
171
|
return this.handleGoAway(message, direction, sideEffects);
|
|
120
172
|
|
|
121
173
|
// Subscribe lifecycle
|
|
122
|
-
case
|
|
174
|
+
case "subscribe":
|
|
123
175
|
return this.handleSubscribe(message, direction, sideEffects);
|
|
124
|
-
case
|
|
176
|
+
case "subscribe_ok":
|
|
125
177
|
return this.handleSubscribeOk(message, direction, sideEffects);
|
|
126
|
-
case
|
|
178
|
+
case "subscribe_error":
|
|
127
179
|
return this.handleSubscribeError(message, direction, sideEffects);
|
|
128
|
-
case
|
|
180
|
+
case "subscribe_update":
|
|
129
181
|
return this.handleSubscribeUpdate(message, direction, sideEffects);
|
|
130
|
-
case
|
|
182
|
+
case "unsubscribe":
|
|
131
183
|
return this.handleUnsubscribe(message, direction, sideEffects);
|
|
132
184
|
|
|
133
185
|
// Publish lifecycle
|
|
134
|
-
case
|
|
186
|
+
case "publish":
|
|
135
187
|
return this.handlePublish(message, direction, sideEffects);
|
|
136
|
-
case
|
|
188
|
+
case "publish_ok":
|
|
137
189
|
return this.handlePublishOk(message, direction, sideEffects);
|
|
138
|
-
case
|
|
190
|
+
case "publish_error":
|
|
139
191
|
return this.handlePublishError(message, direction, sideEffects);
|
|
140
|
-
case
|
|
192
|
+
case "publish_done":
|
|
141
193
|
return this.handlePublishDone(message, direction, sideEffects);
|
|
142
194
|
|
|
143
195
|
// Fetch lifecycle
|
|
144
|
-
case
|
|
196
|
+
case "fetch":
|
|
145
197
|
return this.handleFetch(message, direction, sideEffects);
|
|
146
|
-
case
|
|
198
|
+
case "fetch_ok":
|
|
147
199
|
return this.handleFetchOk(message, direction, sideEffects);
|
|
148
|
-
case
|
|
200
|
+
case "fetch_error":
|
|
149
201
|
return this.handleFetchError(message, direction, sideEffects);
|
|
150
|
-
case
|
|
202
|
+
case "fetch_cancel":
|
|
151
203
|
return this.handleFetchCancel(message, direction, sideEffects);
|
|
152
204
|
|
|
153
|
-
// Publish namespace
|
|
205
|
+
// Publish namespace lifecycle
|
|
206
|
+
case "publish_namespace":
|
|
207
|
+
return this.handlePublishNamespace(message, sideEffects);
|
|
208
|
+
case "publish_namespace_ok":
|
|
209
|
+
return this.handlePublishNamespaceOk(message, sideEffects);
|
|
210
|
+
case "publish_namespace_error":
|
|
211
|
+
return this.handlePublishNamespaceError(message, sideEffects);
|
|
212
|
+
case "publish_namespace_done":
|
|
213
|
+
return this.handlePublishNamespaceDone(message, sideEffects);
|
|
214
|
+
case "publish_namespace_cancel":
|
|
215
|
+
return this.handlePublishNamespaceCancel(message, sideEffects);
|
|
216
|
+
|
|
217
|
+
// Subscribe namespace lifecycle
|
|
218
|
+
case "subscribe_namespace":
|
|
219
|
+
return this.handleSubscribeNamespace(message, sideEffects);
|
|
220
|
+
case "subscribe_namespace_ok":
|
|
221
|
+
return this.handleSubscribeNamespaceOk(message, sideEffects);
|
|
222
|
+
case "subscribe_namespace_error":
|
|
223
|
+
return this.handleSubscribeNamespaceError(message, sideEffects);
|
|
224
|
+
case "unsubscribe_namespace":
|
|
225
|
+
return this.handleUnsubscribeNamespace(message, sideEffects);
|
|
226
|
+
|
|
227
|
+
// Track status lifecycle
|
|
228
|
+
case "track_status":
|
|
229
|
+
return this.handleTrackStatus(message, sideEffects);
|
|
230
|
+
case "track_status_ok":
|
|
231
|
+
return this.handleTrackStatusOk(message, sideEffects);
|
|
232
|
+
case "track_status_error":
|
|
233
|
+
return this.handleTrackStatusError(message, sideEffects);
|
|
234
|
+
|
|
235
|
+
// Other ready-phase messages
|
|
154
236
|
default:
|
|
155
237
|
return this.handleReadyPhaseMessage(message);
|
|
156
238
|
}
|
|
157
239
|
}
|
|
158
240
|
|
|
159
|
-
private handleClientSetup(
|
|
160
|
-
|
|
161
|
-
|
|
241
|
+
private handleClientSetup(
|
|
242
|
+
_message: Draft14Message,
|
|
243
|
+
direction: "inbound" | "outbound",
|
|
244
|
+
): TransitionResult<Draft14MessageType> {
|
|
245
|
+
if (this._phase !== "idle") {
|
|
246
|
+
return {
|
|
247
|
+
ok: false,
|
|
248
|
+
violation: violation(
|
|
249
|
+
"SETUP_VIOLATION",
|
|
250
|
+
"CLIENT_SETUP already sent/received",
|
|
251
|
+
this._phase,
|
|
252
|
+
"client_setup",
|
|
253
|
+
),
|
|
254
|
+
};
|
|
162
255
|
}
|
|
163
256
|
|
|
164
|
-
if (direction ===
|
|
165
|
-
return {
|
|
257
|
+
if (direction === "outbound" && this._role !== "client") {
|
|
258
|
+
return {
|
|
259
|
+
ok: false,
|
|
260
|
+
violation: violation(
|
|
261
|
+
"ROLE_VIOLATION",
|
|
262
|
+
"Only client can send CLIENT_SETUP",
|
|
263
|
+
this._phase,
|
|
264
|
+
"client_setup",
|
|
265
|
+
),
|
|
266
|
+
};
|
|
166
267
|
}
|
|
167
268
|
|
|
168
|
-
this._phase =
|
|
269
|
+
this._phase = "setup";
|
|
169
270
|
return { ok: true, phase: this._phase, sideEffects: [] };
|
|
170
271
|
}
|
|
171
272
|
|
|
172
|
-
private handleServerSetup(
|
|
173
|
-
|
|
174
|
-
|
|
273
|
+
private handleServerSetup(
|
|
274
|
+
_message: Draft14Message,
|
|
275
|
+
direction: "inbound" | "outbound",
|
|
276
|
+
): TransitionResult<Draft14MessageType> {
|
|
277
|
+
if (this._phase !== "setup") {
|
|
278
|
+
return {
|
|
279
|
+
ok: false,
|
|
280
|
+
violation: violation(
|
|
281
|
+
"SETUP_VIOLATION",
|
|
282
|
+
"SERVER_SETUP before CLIENT_SETUP",
|
|
283
|
+
this._phase,
|
|
284
|
+
"server_setup",
|
|
285
|
+
),
|
|
286
|
+
};
|
|
175
287
|
}
|
|
176
288
|
|
|
177
|
-
if (direction ===
|
|
178
|
-
return {
|
|
289
|
+
if (direction === "outbound" && this._role !== "server") {
|
|
290
|
+
return {
|
|
291
|
+
ok: false,
|
|
292
|
+
violation: violation(
|
|
293
|
+
"ROLE_VIOLATION",
|
|
294
|
+
"Only server can send SERVER_SETUP",
|
|
295
|
+
this._phase,
|
|
296
|
+
"server_setup",
|
|
297
|
+
),
|
|
298
|
+
};
|
|
179
299
|
}
|
|
180
300
|
|
|
181
|
-
this._phase =
|
|
182
|
-
return { ok: true, phase: this._phase, sideEffects: [{ type:
|
|
301
|
+
this._phase = "ready";
|
|
302
|
+
return { ok: true, phase: this._phase, sideEffects: [{ type: "session-ready" }] };
|
|
183
303
|
}
|
|
184
304
|
|
|
185
|
-
private handleGoAway(
|
|
186
|
-
|
|
187
|
-
|
|
305
|
+
private handleGoAway(
|
|
306
|
+
message: Draft14Message,
|
|
307
|
+
_direction: "inbound" | "outbound",
|
|
308
|
+
sideEffects: SideEffect[],
|
|
309
|
+
): TransitionResult<Draft14MessageType> {
|
|
310
|
+
if (this._phase !== "ready" && this._phase !== "draining") {
|
|
311
|
+
return {
|
|
312
|
+
ok: false,
|
|
313
|
+
violation: violation(
|
|
314
|
+
"UNEXPECTED_MESSAGE",
|
|
315
|
+
`GOAWAY not valid in phase ${this._phase}`,
|
|
316
|
+
this._phase,
|
|
317
|
+
"goaway",
|
|
318
|
+
),
|
|
319
|
+
};
|
|
188
320
|
}
|
|
189
|
-
this._phase =
|
|
190
|
-
const goaway = message as import(
|
|
191
|
-
sideEffects.push({ type:
|
|
321
|
+
this._phase = "draining";
|
|
322
|
+
const goaway = message as import("./types.js").Draft14GoAway;
|
|
323
|
+
sideEffects.push({ type: "session-draining", goAwayUri: goaway.new_session_uri });
|
|
192
324
|
return { ok: true, phase: this._phase, sideEffects };
|
|
193
325
|
}
|
|
194
326
|
|
|
195
327
|
private requireReady(msgType: Draft14MessageType): ProtocolViolation<Draft14MessageType> | null {
|
|
196
|
-
if (this._phase !==
|
|
328
|
+
if (this._phase !== "ready" && this._phase !== "draining") {
|
|
197
329
|
return violation(
|
|
198
|
-
this._phase ===
|
|
330
|
+
this._phase === "idle" || this._phase === "setup"
|
|
331
|
+
? "MESSAGE_BEFORE_SETUP"
|
|
332
|
+
: "UNEXPECTED_MESSAGE",
|
|
199
333
|
`${msgType} requires ready phase, current: ${this._phase}`,
|
|
200
334
|
this._phase,
|
|
201
335
|
msgType,
|
|
@@ -206,18 +340,22 @@ export class Draft14SessionFSM {
|
|
|
206
340
|
|
|
207
341
|
// ─── Subscribe lifecycle ──────────────────────────────────────────────────────
|
|
208
342
|
|
|
209
|
-
private handleSubscribe(
|
|
343
|
+
private handleSubscribe(
|
|
344
|
+
message: Draft14Message,
|
|
345
|
+
_direction: "inbound" | "outbound",
|
|
346
|
+
sideEffects: SideEffect[],
|
|
347
|
+
): TransitionResult<Draft14MessageType> {
|
|
210
348
|
const err = this.requireReady(message.type);
|
|
211
349
|
if (err) return { ok: false, violation: err };
|
|
212
350
|
|
|
213
|
-
const sub = message as import(
|
|
351
|
+
const sub = message as import("./types.js").Draft14Subscribe;
|
|
214
352
|
const dupErr = this.checkDuplicateRequestId(sub.request_id, message.type);
|
|
215
353
|
if (dupErr) return { ok: false, violation: dupErr };
|
|
216
354
|
|
|
217
355
|
this._requestIds.add(sub.request_id);
|
|
218
356
|
this._subscriptions.set(sub.request_id, {
|
|
219
357
|
subscribeId: sub.request_id,
|
|
220
|
-
phase:
|
|
358
|
+
phase: "pending",
|
|
221
359
|
trackNamespace: sub.track_namespace,
|
|
222
360
|
trackName: sub.track_name,
|
|
223
361
|
});
|
|
@@ -225,240 +363,595 @@ export class Draft14SessionFSM {
|
|
|
225
363
|
return { ok: true, phase: this._phase, sideEffects };
|
|
226
364
|
}
|
|
227
365
|
|
|
228
|
-
private handleSubscribeOk(
|
|
366
|
+
private handleSubscribeOk(
|
|
367
|
+
message: Draft14Message,
|
|
368
|
+
_direction: "inbound" | "outbound",
|
|
369
|
+
sideEffects: SideEffect[],
|
|
370
|
+
): TransitionResult<Draft14MessageType> {
|
|
229
371
|
const err = this.requireReady(message.type);
|
|
230
372
|
if (err) return { ok: false, violation: err };
|
|
231
373
|
|
|
232
|
-
const ok = message as import(
|
|
374
|
+
const ok = message as import("./types.js").Draft14SubscribeOk;
|
|
233
375
|
const idErr = this.checkKnownRequestId(ok.request_id, message.type);
|
|
234
376
|
if (idErr) return { ok: false, violation: idErr };
|
|
235
377
|
|
|
236
378
|
const existing = this._subscriptions.get(ok.request_id);
|
|
237
379
|
if (!existing) {
|
|
238
|
-
return {
|
|
380
|
+
return {
|
|
381
|
+
ok: false,
|
|
382
|
+
violation: violation(
|
|
383
|
+
"UNKNOWN_REQUEST_ID",
|
|
384
|
+
`No subscription with request ID ${ok.request_id}`,
|
|
385
|
+
this._phase,
|
|
386
|
+
message.type,
|
|
387
|
+
),
|
|
388
|
+
};
|
|
239
389
|
}
|
|
240
|
-
if (existing.phase !==
|
|
241
|
-
return {
|
|
390
|
+
if (existing.phase !== "pending") {
|
|
391
|
+
return {
|
|
392
|
+
ok: false,
|
|
393
|
+
violation: violation(
|
|
394
|
+
"STATE_VIOLATION",
|
|
395
|
+
`Subscription ${ok.request_id} is ${existing.phase}, not pending`,
|
|
396
|
+
this._phase,
|
|
397
|
+
message.type,
|
|
398
|
+
),
|
|
399
|
+
};
|
|
242
400
|
}
|
|
243
401
|
|
|
244
|
-
this._subscriptions.set(ok.request_id, { ...existing, phase:
|
|
245
|
-
sideEffects.push({ type:
|
|
402
|
+
this._subscriptions.set(ok.request_id, { ...existing, phase: "active" });
|
|
403
|
+
sideEffects.push({ type: "subscription-activated", subscribeId: ok.request_id });
|
|
246
404
|
return { ok: true, phase: this._phase, sideEffects };
|
|
247
405
|
}
|
|
248
406
|
|
|
249
|
-
private handleSubscribeError(
|
|
407
|
+
private handleSubscribeError(
|
|
408
|
+
message: Draft14Message,
|
|
409
|
+
_direction: "inbound" | "outbound",
|
|
410
|
+
sideEffects: SideEffect[],
|
|
411
|
+
): TransitionResult<Draft14MessageType> {
|
|
250
412
|
const err = this.requireReady(message.type);
|
|
251
413
|
if (err) return { ok: false, violation: err };
|
|
252
414
|
|
|
253
|
-
const subErr = message as import(
|
|
415
|
+
const subErr = message as import("./types.js").Draft14SubscribeError;
|
|
254
416
|
const idErr = this.checkKnownRequestId(subErr.request_id, message.type);
|
|
255
417
|
if (idErr) return { ok: false, violation: idErr };
|
|
256
418
|
|
|
257
419
|
const existing = this._subscriptions.get(subErr.request_id);
|
|
258
420
|
if (!existing) {
|
|
259
|
-
return {
|
|
421
|
+
return {
|
|
422
|
+
ok: false,
|
|
423
|
+
violation: violation(
|
|
424
|
+
"UNKNOWN_REQUEST_ID",
|
|
425
|
+
`No subscription with request ID ${subErr.request_id}`,
|
|
426
|
+
this._phase,
|
|
427
|
+
message.type,
|
|
428
|
+
),
|
|
429
|
+
};
|
|
260
430
|
}
|
|
261
|
-
if (existing.phase !==
|
|
262
|
-
return {
|
|
431
|
+
if (existing.phase !== "pending") {
|
|
432
|
+
return {
|
|
433
|
+
ok: false,
|
|
434
|
+
violation: violation(
|
|
435
|
+
"STATE_VIOLATION",
|
|
436
|
+
`Subscription ${subErr.request_id} is ${existing.phase}, not pending`,
|
|
437
|
+
this._phase,
|
|
438
|
+
message.type,
|
|
439
|
+
),
|
|
440
|
+
};
|
|
263
441
|
}
|
|
264
442
|
|
|
265
|
-
this._subscriptions.set(subErr.request_id, { ...existing, phase:
|
|
266
|
-
sideEffects.push({
|
|
443
|
+
this._subscriptions.set(subErr.request_id, { ...existing, phase: "error" });
|
|
444
|
+
sideEffects.push({
|
|
445
|
+
type: "subscription-ended",
|
|
446
|
+
subscribeId: subErr.request_id,
|
|
447
|
+
reason: subErr.reason_phrase,
|
|
448
|
+
});
|
|
267
449
|
return { ok: true, phase: this._phase, sideEffects };
|
|
268
450
|
}
|
|
269
451
|
|
|
270
|
-
private handleSubscribeUpdate(
|
|
452
|
+
private handleSubscribeUpdate(
|
|
453
|
+
message: Draft14Message,
|
|
454
|
+
_direction: "inbound" | "outbound",
|
|
455
|
+
sideEffects: SideEffect[],
|
|
456
|
+
): TransitionResult<Draft14MessageType> {
|
|
271
457
|
const err = this.requireReady(message.type);
|
|
272
458
|
if (err) return { ok: false, violation: err };
|
|
273
459
|
|
|
274
|
-
const update = message as import(
|
|
460
|
+
const update = message as import("./types.js").Draft14SubscribeUpdate;
|
|
275
461
|
const idErr = this.checkKnownRequestId(update.request_id, message.type);
|
|
276
462
|
if (idErr) return { ok: false, violation: idErr };
|
|
277
463
|
|
|
278
464
|
const existing = this._subscriptions.get(update.request_id);
|
|
279
465
|
if (!existing) {
|
|
280
|
-
return {
|
|
466
|
+
return {
|
|
467
|
+
ok: false,
|
|
468
|
+
violation: violation(
|
|
469
|
+
"UNKNOWN_REQUEST_ID",
|
|
470
|
+
`No subscription with request ID ${update.request_id}`,
|
|
471
|
+
this._phase,
|
|
472
|
+
message.type,
|
|
473
|
+
),
|
|
474
|
+
};
|
|
281
475
|
}
|
|
282
|
-
if (existing.phase !==
|
|
283
|
-
return {
|
|
476
|
+
if (existing.phase !== "active") {
|
|
477
|
+
return {
|
|
478
|
+
ok: false,
|
|
479
|
+
violation: violation(
|
|
480
|
+
"STATE_VIOLATION",
|
|
481
|
+
`Subscription ${update.request_id} is ${existing.phase}, not active`,
|
|
482
|
+
this._phase,
|
|
483
|
+
message.type,
|
|
484
|
+
),
|
|
485
|
+
};
|
|
284
486
|
}
|
|
285
487
|
|
|
286
488
|
return { ok: true, phase: this._phase, sideEffects };
|
|
287
489
|
}
|
|
288
490
|
|
|
289
|
-
private handleUnsubscribe(
|
|
491
|
+
private handleUnsubscribe(
|
|
492
|
+
message: Draft14Message,
|
|
493
|
+
_direction: "inbound" | "outbound",
|
|
494
|
+
sideEffects: SideEffect[],
|
|
495
|
+
): TransitionResult<Draft14MessageType> {
|
|
290
496
|
const err = this.requireReady(message.type);
|
|
291
497
|
if (err) return { ok: false, violation: err };
|
|
292
498
|
|
|
293
|
-
const unsub = message as import(
|
|
499
|
+
const unsub = message as import("./types.js").Draft14Unsubscribe;
|
|
294
500
|
const idErr = this.checkKnownRequestId(unsub.request_id, message.type);
|
|
295
501
|
if (idErr) return { ok: false, violation: idErr };
|
|
296
502
|
|
|
297
503
|
const existing = this._subscriptions.get(unsub.request_id);
|
|
298
504
|
if (!existing) {
|
|
299
|
-
return {
|
|
505
|
+
return {
|
|
506
|
+
ok: false,
|
|
507
|
+
violation: violation(
|
|
508
|
+
"UNKNOWN_REQUEST_ID",
|
|
509
|
+
`No subscription with request ID ${unsub.request_id}`,
|
|
510
|
+
this._phase,
|
|
511
|
+
message.type,
|
|
512
|
+
),
|
|
513
|
+
};
|
|
300
514
|
}
|
|
301
515
|
|
|
302
|
-
this._subscriptions.set(unsub.request_id, { ...existing, phase:
|
|
303
|
-
sideEffects.push({
|
|
516
|
+
this._subscriptions.set(unsub.request_id, { ...existing, phase: "done" });
|
|
517
|
+
sideEffects.push({
|
|
518
|
+
type: "subscription-ended",
|
|
519
|
+
subscribeId: unsub.request_id,
|
|
520
|
+
reason: "unsubscribed",
|
|
521
|
+
});
|
|
304
522
|
return { ok: true, phase: this._phase, sideEffects };
|
|
305
523
|
}
|
|
306
524
|
|
|
307
525
|
// ─── Publish lifecycle ────────────────────────────────────────────────────────
|
|
308
526
|
|
|
309
|
-
private handlePublish(
|
|
527
|
+
private handlePublish(
|
|
528
|
+
message: Draft14Message,
|
|
529
|
+
_direction: "inbound" | "outbound",
|
|
530
|
+
sideEffects: SideEffect[],
|
|
531
|
+
): TransitionResult<Draft14MessageType> {
|
|
310
532
|
const err = this.requireReady(message.type);
|
|
311
533
|
if (err) return { ok: false, violation: err };
|
|
312
534
|
|
|
313
|
-
const pub = message as import(
|
|
535
|
+
const pub = message as import("./types.js").Draft14Publish;
|
|
314
536
|
const dupErr = this.checkDuplicateRequestId(pub.request_id, message.type);
|
|
315
537
|
if (dupErr) return { ok: false, violation: dupErr };
|
|
316
538
|
|
|
317
539
|
this._requestIds.add(pub.request_id);
|
|
318
540
|
this._publishes.set(pub.request_id, {
|
|
319
541
|
requestId: pub.request_id,
|
|
320
|
-
phase:
|
|
542
|
+
phase: "pending",
|
|
321
543
|
});
|
|
322
544
|
|
|
323
545
|
return { ok: true, phase: this._phase, sideEffects };
|
|
324
546
|
}
|
|
325
547
|
|
|
326
|
-
private handlePublishOk(
|
|
548
|
+
private handlePublishOk(
|
|
549
|
+
message: Draft14Message,
|
|
550
|
+
_direction: "inbound" | "outbound",
|
|
551
|
+
sideEffects: SideEffect[],
|
|
552
|
+
): TransitionResult<Draft14MessageType> {
|
|
327
553
|
const err = this.requireReady(message.type);
|
|
328
554
|
if (err) return { ok: false, violation: err };
|
|
329
555
|
|
|
330
|
-
const ok = message as import(
|
|
556
|
+
const ok = message as import("./types.js").Draft14PublishOk;
|
|
331
557
|
const idErr = this.checkKnownRequestId(ok.request_id, message.type);
|
|
332
558
|
if (idErr) return { ok: false, violation: idErr };
|
|
333
559
|
|
|
334
560
|
const existing = this._publishes.get(ok.request_id);
|
|
335
561
|
if (!existing) {
|
|
336
|
-
return {
|
|
562
|
+
return {
|
|
563
|
+
ok: false,
|
|
564
|
+
violation: violation(
|
|
565
|
+
"UNKNOWN_REQUEST_ID",
|
|
566
|
+
`No publish with request ID ${ok.request_id}`,
|
|
567
|
+
this._phase,
|
|
568
|
+
message.type,
|
|
569
|
+
),
|
|
570
|
+
};
|
|
337
571
|
}
|
|
338
|
-
if (existing.phase !==
|
|
339
|
-
return {
|
|
572
|
+
if (existing.phase !== "pending") {
|
|
573
|
+
return {
|
|
574
|
+
ok: false,
|
|
575
|
+
violation: violation(
|
|
576
|
+
"STATE_VIOLATION",
|
|
577
|
+
`Publish ${ok.request_id} is ${existing.phase}, not pending`,
|
|
578
|
+
this._phase,
|
|
579
|
+
message.type,
|
|
580
|
+
),
|
|
581
|
+
};
|
|
340
582
|
}
|
|
341
583
|
|
|
342
|
-
this._publishes.set(ok.request_id, { ...existing, phase:
|
|
343
|
-
sideEffects.push({ type:
|
|
584
|
+
this._publishes.set(ok.request_id, { ...existing, phase: "active" });
|
|
585
|
+
sideEffects.push({ type: "publish-activated", requestId: ok.request_id });
|
|
344
586
|
return { ok: true, phase: this._phase, sideEffects };
|
|
345
587
|
}
|
|
346
588
|
|
|
347
|
-
private handlePublishError(
|
|
589
|
+
private handlePublishError(
|
|
590
|
+
message: Draft14Message,
|
|
591
|
+
_direction: "inbound" | "outbound",
|
|
592
|
+
sideEffects: SideEffect[],
|
|
593
|
+
): TransitionResult<Draft14MessageType> {
|
|
348
594
|
const err = this.requireReady(message.type);
|
|
349
595
|
if (err) return { ok: false, violation: err };
|
|
350
596
|
|
|
351
|
-
const pubErr = message as import(
|
|
597
|
+
const pubErr = message as import("./types.js").Draft14PublishError;
|
|
352
598
|
const idErr = this.checkKnownRequestId(pubErr.request_id, message.type);
|
|
353
599
|
if (idErr) return { ok: false, violation: idErr };
|
|
354
600
|
|
|
355
601
|
const existing = this._publishes.get(pubErr.request_id);
|
|
356
602
|
if (!existing) {
|
|
357
|
-
return {
|
|
603
|
+
return {
|
|
604
|
+
ok: false,
|
|
605
|
+
violation: violation(
|
|
606
|
+
"UNKNOWN_REQUEST_ID",
|
|
607
|
+
`No publish with request ID ${pubErr.request_id}`,
|
|
608
|
+
this._phase,
|
|
609
|
+
message.type,
|
|
610
|
+
),
|
|
611
|
+
};
|
|
358
612
|
}
|
|
359
|
-
if (existing.phase !==
|
|
360
|
-
return {
|
|
613
|
+
if (existing.phase !== "pending") {
|
|
614
|
+
return {
|
|
615
|
+
ok: false,
|
|
616
|
+
violation: violation(
|
|
617
|
+
"STATE_VIOLATION",
|
|
618
|
+
`Publish ${pubErr.request_id} is ${existing.phase}, not pending`,
|
|
619
|
+
this._phase,
|
|
620
|
+
message.type,
|
|
621
|
+
),
|
|
622
|
+
};
|
|
361
623
|
}
|
|
362
624
|
|
|
363
|
-
this._publishes.set(pubErr.request_id, { ...existing, phase:
|
|
364
|
-
sideEffects.push({
|
|
625
|
+
this._publishes.set(pubErr.request_id, { ...existing, phase: "error" });
|
|
626
|
+
sideEffects.push({
|
|
627
|
+
type: "publish-ended",
|
|
628
|
+
requestId: pubErr.request_id,
|
|
629
|
+
reason: pubErr.reason_phrase,
|
|
630
|
+
});
|
|
365
631
|
return { ok: true, phase: this._phase, sideEffects };
|
|
366
632
|
}
|
|
367
633
|
|
|
368
|
-
private handlePublishDone(
|
|
634
|
+
private handlePublishDone(
|
|
635
|
+
message: Draft14Message,
|
|
636
|
+
_direction: "inbound" | "outbound",
|
|
637
|
+
sideEffects: SideEffect[],
|
|
638
|
+
): TransitionResult<Draft14MessageType> {
|
|
369
639
|
const err = this.requireReady(message.type);
|
|
370
640
|
if (err) return { ok: false, violation: err };
|
|
371
641
|
|
|
372
|
-
const done = message as import(
|
|
642
|
+
const done = message as import("./types.js").Draft14PublishDone;
|
|
373
643
|
const idErr = this.checkKnownRequestId(done.request_id, message.type);
|
|
374
644
|
if (idErr) return { ok: false, violation: idErr };
|
|
375
645
|
|
|
376
646
|
const existing = this._publishes.get(done.request_id);
|
|
377
647
|
if (!existing) {
|
|
378
|
-
return {
|
|
648
|
+
return {
|
|
649
|
+
ok: false,
|
|
650
|
+
violation: violation(
|
|
651
|
+
"UNKNOWN_REQUEST_ID",
|
|
652
|
+
`No publish with request ID ${done.request_id}`,
|
|
653
|
+
this._phase,
|
|
654
|
+
message.type,
|
|
655
|
+
),
|
|
656
|
+
};
|
|
379
657
|
}
|
|
380
658
|
|
|
381
|
-
this._publishes.set(done.request_id, { ...existing, phase:
|
|
382
|
-
sideEffects.push({
|
|
659
|
+
this._publishes.set(done.request_id, { ...existing, phase: "done" });
|
|
660
|
+
sideEffects.push({
|
|
661
|
+
type: "publish-ended",
|
|
662
|
+
requestId: done.request_id,
|
|
663
|
+
reason: done.reason_phrase,
|
|
664
|
+
});
|
|
383
665
|
return { ok: true, phase: this._phase, sideEffects };
|
|
384
666
|
}
|
|
385
667
|
|
|
386
668
|
// ─── Fetch lifecycle ──────────────────────────────────────────────────────────
|
|
387
669
|
|
|
388
|
-
private handleFetch(
|
|
670
|
+
private handleFetch(
|
|
671
|
+
message: Draft14Message,
|
|
672
|
+
_direction: "inbound" | "outbound",
|
|
673
|
+
sideEffects: SideEffect[],
|
|
674
|
+
): TransitionResult<Draft14MessageType> {
|
|
389
675
|
const err = this.requireReady(message.type);
|
|
390
676
|
if (err) return { ok: false, violation: err };
|
|
391
677
|
|
|
392
|
-
const fetch = message as import(
|
|
678
|
+
const fetch = message as import("./types.js").Draft14Fetch;
|
|
393
679
|
const dupErr = this.checkDuplicateRequestId(fetch.request_id, message.type);
|
|
394
680
|
if (dupErr) return { ok: false, violation: dupErr };
|
|
395
681
|
|
|
396
682
|
this._requestIds.add(fetch.request_id);
|
|
397
683
|
this._fetches.set(fetch.request_id, {
|
|
398
684
|
requestId: fetch.request_id,
|
|
399
|
-
phase:
|
|
685
|
+
phase: "pending",
|
|
400
686
|
});
|
|
401
687
|
|
|
402
688
|
return { ok: true, phase: this._phase, sideEffects };
|
|
403
689
|
}
|
|
404
690
|
|
|
405
|
-
private handleFetchOk(
|
|
691
|
+
private handleFetchOk(
|
|
692
|
+
message: Draft14Message,
|
|
693
|
+
_direction: "inbound" | "outbound",
|
|
694
|
+
sideEffects: SideEffect[],
|
|
695
|
+
): TransitionResult<Draft14MessageType> {
|
|
406
696
|
const err = this.requireReady(message.type);
|
|
407
697
|
if (err) return { ok: false, violation: err };
|
|
408
698
|
|
|
409
|
-
const ok = message as import(
|
|
699
|
+
const ok = message as import("./types.js").Draft14FetchOk;
|
|
410
700
|
const idErr = this.checkKnownRequestId(ok.request_id, message.type);
|
|
411
701
|
if (idErr) return { ok: false, violation: idErr };
|
|
412
702
|
|
|
413
703
|
const existing = this._fetches.get(ok.request_id);
|
|
414
704
|
if (!existing) {
|
|
415
|
-
return {
|
|
705
|
+
return {
|
|
706
|
+
ok: false,
|
|
707
|
+
violation: violation(
|
|
708
|
+
"UNKNOWN_REQUEST_ID",
|
|
709
|
+
`No fetch with request ID ${ok.request_id}`,
|
|
710
|
+
this._phase,
|
|
711
|
+
message.type,
|
|
712
|
+
),
|
|
713
|
+
};
|
|
416
714
|
}
|
|
417
|
-
if (existing.phase !==
|
|
418
|
-
return {
|
|
715
|
+
if (existing.phase !== "pending") {
|
|
716
|
+
return {
|
|
717
|
+
ok: false,
|
|
718
|
+
violation: violation(
|
|
719
|
+
"STATE_VIOLATION",
|
|
720
|
+
`Fetch ${ok.request_id} is ${existing.phase}, not pending`,
|
|
721
|
+
this._phase,
|
|
722
|
+
message.type,
|
|
723
|
+
),
|
|
724
|
+
};
|
|
419
725
|
}
|
|
420
726
|
|
|
421
|
-
this._fetches.set(ok.request_id, { ...existing, phase:
|
|
422
|
-
sideEffects.push({ type:
|
|
727
|
+
this._fetches.set(ok.request_id, { ...existing, phase: "active" });
|
|
728
|
+
sideEffects.push({ type: "fetch-activated", requestId: ok.request_id });
|
|
423
729
|
return { ok: true, phase: this._phase, sideEffects };
|
|
424
730
|
}
|
|
425
731
|
|
|
426
|
-
private handleFetchError(
|
|
732
|
+
private handleFetchError(
|
|
733
|
+
message: Draft14Message,
|
|
734
|
+
_direction: "inbound" | "outbound",
|
|
735
|
+
sideEffects: SideEffect[],
|
|
736
|
+
): TransitionResult<Draft14MessageType> {
|
|
427
737
|
const err = this.requireReady(message.type);
|
|
428
738
|
if (err) return { ok: false, violation: err };
|
|
429
739
|
|
|
430
|
-
const fetchErr = message as import(
|
|
740
|
+
const fetchErr = message as import("./types.js").Draft14FetchError;
|
|
431
741
|
const idErr = this.checkKnownRequestId(fetchErr.request_id, message.type);
|
|
432
742
|
if (idErr) return { ok: false, violation: idErr };
|
|
433
743
|
|
|
434
744
|
const existing = this._fetches.get(fetchErr.request_id);
|
|
435
745
|
if (!existing) {
|
|
436
|
-
return {
|
|
746
|
+
return {
|
|
747
|
+
ok: false,
|
|
748
|
+
violation: violation(
|
|
749
|
+
"UNKNOWN_REQUEST_ID",
|
|
750
|
+
`No fetch with request ID ${fetchErr.request_id}`,
|
|
751
|
+
this._phase,
|
|
752
|
+
message.type,
|
|
753
|
+
),
|
|
754
|
+
};
|
|
437
755
|
}
|
|
438
|
-
if (existing.phase !==
|
|
439
|
-
return {
|
|
756
|
+
if (existing.phase !== "pending") {
|
|
757
|
+
return {
|
|
758
|
+
ok: false,
|
|
759
|
+
violation: violation(
|
|
760
|
+
"STATE_VIOLATION",
|
|
761
|
+
`Fetch ${fetchErr.request_id} is ${existing.phase}, not pending`,
|
|
762
|
+
this._phase,
|
|
763
|
+
message.type,
|
|
764
|
+
),
|
|
765
|
+
};
|
|
440
766
|
}
|
|
441
767
|
|
|
442
|
-
this._fetches.set(fetchErr.request_id, { ...existing, phase:
|
|
443
|
-
sideEffects.push({
|
|
768
|
+
this._fetches.set(fetchErr.request_id, { ...existing, phase: "error" });
|
|
769
|
+
sideEffects.push({
|
|
770
|
+
type: "fetch-ended",
|
|
771
|
+
requestId: fetchErr.request_id,
|
|
772
|
+
reason: fetchErr.reason_phrase,
|
|
773
|
+
});
|
|
444
774
|
return { ok: true, phase: this._phase, sideEffects };
|
|
445
775
|
}
|
|
446
776
|
|
|
447
|
-
private handleFetchCancel(
|
|
777
|
+
private handleFetchCancel(
|
|
778
|
+
message: Draft14Message,
|
|
779
|
+
_direction: "inbound" | "outbound",
|
|
780
|
+
sideEffects: SideEffect[],
|
|
781
|
+
): TransitionResult<Draft14MessageType> {
|
|
448
782
|
const err = this.requireReady(message.type);
|
|
449
783
|
if (err) return { ok: false, violation: err };
|
|
450
784
|
|
|
451
|
-
const cancel = message as import(
|
|
785
|
+
const cancel = message as import("./types.js").Draft14FetchCancel;
|
|
452
786
|
const idErr = this.checkKnownRequestId(cancel.request_id, message.type);
|
|
453
787
|
if (idErr) return { ok: false, violation: idErr };
|
|
454
788
|
|
|
455
789
|
const existing = this._fetches.get(cancel.request_id);
|
|
456
790
|
if (!existing) {
|
|
457
|
-
return {
|
|
791
|
+
return {
|
|
792
|
+
ok: false,
|
|
793
|
+
violation: violation(
|
|
794
|
+
"UNKNOWN_REQUEST_ID",
|
|
795
|
+
`No fetch with request ID ${cancel.request_id}`,
|
|
796
|
+
this._phase,
|
|
797
|
+
message.type,
|
|
798
|
+
),
|
|
799
|
+
};
|
|
458
800
|
}
|
|
459
801
|
|
|
460
|
-
this._fetches.set(cancel.request_id, { ...existing, phase:
|
|
461
|
-
sideEffects.push({ type:
|
|
802
|
+
this._fetches.set(cancel.request_id, { ...existing, phase: "cancelled" });
|
|
803
|
+
sideEffects.push({ type: "fetch-ended", requestId: cancel.request_id, reason: "cancelled" });
|
|
804
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// ─── Publish namespace lifecycle ──────────────────────────────────────────────
|
|
808
|
+
|
|
809
|
+
private handlePublishNamespace(
|
|
810
|
+
message: Draft14Message,
|
|
811
|
+
sideEffects: SideEffect[],
|
|
812
|
+
): TransitionResult<Draft14MessageType> {
|
|
813
|
+
const err = this.requireReady(message.type);
|
|
814
|
+
if (err) return { ok: false, violation: err };
|
|
815
|
+
const pn = message as import("./types.js").Draft14PublishNamespace;
|
|
816
|
+
const dupErr = this.checkDuplicateRequestId(pn.request_id, message.type);
|
|
817
|
+
if (dupErr) return { ok: false, violation: dupErr };
|
|
818
|
+
this._requestIds.add(pn.request_id);
|
|
819
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
private handlePublishNamespaceOk(
|
|
823
|
+
message: Draft14Message,
|
|
824
|
+
sideEffects: SideEffect[],
|
|
825
|
+
): TransitionResult<Draft14MessageType> {
|
|
826
|
+
const err = this.requireReady(message.type);
|
|
827
|
+
if (err) return { ok: false, violation: err };
|
|
828
|
+
const ok = message as import("./types.js").Draft14PublishNamespaceOk;
|
|
829
|
+
const idErr = this.checkKnownRequestId(ok.request_id, message.type);
|
|
830
|
+
if (idErr) return { ok: false, violation: idErr };
|
|
831
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
private handlePublishNamespaceError(
|
|
835
|
+
message: Draft14Message,
|
|
836
|
+
sideEffects: SideEffect[],
|
|
837
|
+
): TransitionResult<Draft14MessageType> {
|
|
838
|
+
const err = this.requireReady(message.type);
|
|
839
|
+
if (err) return { ok: false, violation: err };
|
|
840
|
+
const pnErr = message as import("./types.js").Draft14PublishNamespaceError;
|
|
841
|
+
const idErr = this.checkKnownRequestId(pnErr.request_id, message.type);
|
|
842
|
+
if (idErr) return { ok: false, violation: idErr };
|
|
843
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
private handlePublishNamespaceDone(
|
|
847
|
+
message: Draft14Message,
|
|
848
|
+
sideEffects: SideEffect[],
|
|
849
|
+
): TransitionResult<Draft14MessageType> {
|
|
850
|
+
const err = this.requireReady(message.type);
|
|
851
|
+
if (err) return { ok: false, violation: err };
|
|
852
|
+
const done = message as import("./types.js").Draft14PublishNamespaceDone;
|
|
853
|
+
const idErr = this.checkKnownRequestId(done.request_id, message.type);
|
|
854
|
+
if (idErr) return { ok: false, violation: idErr };
|
|
855
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
private handlePublishNamespaceCancel(
|
|
859
|
+
message: Draft14Message,
|
|
860
|
+
sideEffects: SideEffect[],
|
|
861
|
+
): TransitionResult<Draft14MessageType> {
|
|
862
|
+
const err = this.requireReady(message.type);
|
|
863
|
+
if (err) return { ok: false, violation: err };
|
|
864
|
+
const cancel = message as import("./types.js").Draft14PublishNamespaceCancel;
|
|
865
|
+
const idErr = this.checkKnownRequestId(cancel.request_id, message.type);
|
|
866
|
+
if (idErr) return { ok: false, violation: idErr };
|
|
867
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// ─── Subscribe namespace lifecycle ──────────────────────────────────────────────
|
|
871
|
+
|
|
872
|
+
private handleSubscribeNamespace(
|
|
873
|
+
message: Draft14Message,
|
|
874
|
+
sideEffects: SideEffect[],
|
|
875
|
+
): TransitionResult<Draft14MessageType> {
|
|
876
|
+
const err = this.requireReady(message.type);
|
|
877
|
+
if (err) return { ok: false, violation: err };
|
|
878
|
+
const sn = message as import("./types.js").Draft14SubscribeNamespace;
|
|
879
|
+
const dupErr = this.checkDuplicateRequestId(sn.request_id, message.type);
|
|
880
|
+
if (dupErr) return { ok: false, violation: dupErr };
|
|
881
|
+
this._requestIds.add(sn.request_id);
|
|
882
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
private handleSubscribeNamespaceOk(
|
|
886
|
+
message: Draft14Message,
|
|
887
|
+
sideEffects: SideEffect[],
|
|
888
|
+
): TransitionResult<Draft14MessageType> {
|
|
889
|
+
const err = this.requireReady(message.type);
|
|
890
|
+
if (err) return { ok: false, violation: err };
|
|
891
|
+
const ok = message as import("./types.js").Draft14SubscribeNamespaceOk;
|
|
892
|
+
const idErr = this.checkKnownRequestId(ok.request_id, message.type);
|
|
893
|
+
if (idErr) return { ok: false, violation: idErr };
|
|
894
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
private handleSubscribeNamespaceError(
|
|
898
|
+
message: Draft14Message,
|
|
899
|
+
sideEffects: SideEffect[],
|
|
900
|
+
): TransitionResult<Draft14MessageType> {
|
|
901
|
+
const err = this.requireReady(message.type);
|
|
902
|
+
if (err) return { ok: false, violation: err };
|
|
903
|
+
const snErr = message as import("./types.js").Draft14SubscribeNamespaceError;
|
|
904
|
+
const idErr = this.checkKnownRequestId(snErr.request_id, message.type);
|
|
905
|
+
if (idErr) return { ok: false, violation: idErr };
|
|
906
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
private handleUnsubscribeNamespace(
|
|
910
|
+
message: Draft14Message,
|
|
911
|
+
sideEffects: SideEffect[],
|
|
912
|
+
): TransitionResult<Draft14MessageType> {
|
|
913
|
+
const err = this.requireReady(message.type);
|
|
914
|
+
if (err) return { ok: false, violation: err };
|
|
915
|
+
// unsubscribe_namespace has no request_id — namespace-based
|
|
916
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// ─── Track status lifecycle ──────────────────────────────────────────────────
|
|
920
|
+
|
|
921
|
+
private handleTrackStatus(
|
|
922
|
+
message: Draft14Message,
|
|
923
|
+
sideEffects: SideEffect[],
|
|
924
|
+
): TransitionResult<Draft14MessageType> {
|
|
925
|
+
const err = this.requireReady(message.type);
|
|
926
|
+
if (err) return { ok: false, violation: err };
|
|
927
|
+
const ts = message as import("./types.js").Draft14TrackStatus;
|
|
928
|
+
const dupErr = this.checkDuplicateRequestId(ts.request_id, message.type);
|
|
929
|
+
if (dupErr) return { ok: false, violation: dupErr };
|
|
930
|
+
this._requestIds.add(ts.request_id);
|
|
931
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private handleTrackStatusOk(
|
|
935
|
+
message: Draft14Message,
|
|
936
|
+
sideEffects: SideEffect[],
|
|
937
|
+
): TransitionResult<Draft14MessageType> {
|
|
938
|
+
const err = this.requireReady(message.type);
|
|
939
|
+
if (err) return { ok: false, violation: err };
|
|
940
|
+
const ok = message as import("./types.js").Draft14TrackStatusOk;
|
|
941
|
+
const idErr = this.checkKnownRequestId(ok.request_id, message.type);
|
|
942
|
+
if (idErr) return { ok: false, violation: idErr };
|
|
943
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
private handleTrackStatusError(
|
|
947
|
+
message: Draft14Message,
|
|
948
|
+
sideEffects: SideEffect[],
|
|
949
|
+
): TransitionResult<Draft14MessageType> {
|
|
950
|
+
const err = this.requireReady(message.type);
|
|
951
|
+
if (err) return { ok: false, violation: err };
|
|
952
|
+
const tsErr = message as import("./types.js").Draft14TrackStatusError;
|
|
953
|
+
const idErr = this.checkKnownRequestId(tsErr.request_id, message.type);
|
|
954
|
+
if (idErr) return { ok: false, violation: idErr };
|
|
462
955
|
return { ok: true, phase: this._phase, sideEffects };
|
|
463
956
|
}
|
|
464
957
|
|
|
@@ -471,7 +964,7 @@ export class Draft14SessionFSM {
|
|
|
471
964
|
}
|
|
472
965
|
|
|
473
966
|
reset(): void {
|
|
474
|
-
this._phase =
|
|
967
|
+
this._phase = "idle";
|
|
475
968
|
this._subscriptions.clear();
|
|
476
969
|
this._publishes.clear();
|
|
477
970
|
this._fetches.clear();
|