@moqtap/codec 0.7.0 → 0.8.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 (54) hide show
  1. package/README.md +24 -0
  2. package/dist/{chunk-5XZKO2U5.js → chunk-2XH6PHXR.js} +62 -51
  3. package/dist/{chunk-APTV6F72.js → chunk-3YAR3I3S.js} +67 -51
  4. package/dist/{chunk-Y4FRXYX6.cjs → chunk-7ENEWAMZ.cjs} +52 -58
  5. package/dist/{chunk-MD2OA7SE.js → chunk-BV6MITLO.js} +15 -8
  6. package/dist/{chunk-B56AG5HU.cjs → chunk-EVCPBFSR.cjs} +6 -4
  7. package/dist/{chunk-KNLPX3YA.js → chunk-H6DTAIWM.js} +6 -4
  8. package/dist/{chunk-J3ASFPCX.cjs → chunk-IADVBKEJ.cjs} +15 -8
  9. package/dist/{chunk-I55J3KXD.js → chunk-NH4L6NIY.js} +48 -33
  10. package/dist/{chunk-EQAYMCXO.cjs → chunk-PU2KV7VP.cjs} +48 -33
  11. package/dist/{chunk-HEO2JG6C.js → chunk-TMXRYOBS.js} +100 -92
  12. package/dist/{chunk-IEJULBKB.cjs → chunk-UA5XFP4O.cjs} +100 -92
  13. package/dist/{chunk-KTVN4P6J.cjs → chunk-UZO5Z2XT.cjs} +62 -51
  14. package/dist/{chunk-MH7537FB.cjs → chunk-VWXUT5R2.cjs} +67 -51
  15. package/dist/{chunk-DORX55MO.js → chunk-XQGEOFGE.js} +52 -58
  16. package/dist/{codec-B1snaM9L.d.cts → codec-N3g1lxQQ.d.cts} +1 -1
  17. package/dist/{codec-D49OofBr.d.ts → codec-v6Vloc7H.d.ts} +1 -1
  18. package/dist/draft12.cjs +2 -2
  19. package/dist/draft12.js +1 -1
  20. package/dist/draft14-session.d.cts +1 -1
  21. package/dist/draft14-session.d.ts +1 -1
  22. package/dist/draft14.cjs +2 -2
  23. package/dist/draft14.d.cts +3 -3
  24. package/dist/draft14.d.ts +3 -3
  25. package/dist/draft14.js +1 -1
  26. package/dist/draft15.cjs +2 -2
  27. package/dist/draft15.js +1 -1
  28. package/dist/draft16.cjs +2 -2
  29. package/dist/draft16.js +1 -1
  30. package/dist/draft17.cjs +2 -2
  31. package/dist/draft17.js +1 -1
  32. package/dist/draft18.cjs +2 -2
  33. package/dist/draft18.js +1 -1
  34. package/dist/draft19.cjs +2 -2
  35. package/dist/draft19.js +1 -1
  36. package/dist/index.cjs +68 -16
  37. package/dist/index.d.cts +73 -3
  38. package/dist/index.d.ts +73 -3
  39. package/dist/index.js +60 -8
  40. package/dist/session.d.cts +1 -1
  41. package/dist/session.d.ts +1 -1
  42. package/dist/{types-DjmrHp_s.d.cts → types-HI_bbXka.d.cts} +29 -23
  43. package/dist/{types-DjmrHp_s.d.ts → types-HI_bbXka.d.ts} +29 -23
  44. package/package.json +2 -2
  45. package/src/core/accessors.ts +120 -0
  46. package/src/drafts/draft12/data-streams.ts +13 -4
  47. package/src/drafts/draft14/codec.ts +52 -58
  48. package/src/drafts/draft14/types.ts +31 -25
  49. package/src/drafts/draft15/data-streams.ts +30 -16
  50. package/src/drafts/draft16/codec.ts +116 -93
  51. package/src/drafts/draft17/codec.ts +72 -35
  52. package/src/drafts/draft18/codec.ts +89 -53
  53. package/src/drafts/draft19/codec.ts +94 -53
  54. package/src/index.ts +8 -0
package/dist/index.js CHANGED
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  createDraft18Codec
3
- } from "./chunk-5XZKO2U5.js";
3
+ } from "./chunk-2XH6PHXR.js";
4
4
  import {
5
5
  createDraft19Codec
6
- } from "./chunk-APTV6F72.js";
6
+ } from "./chunk-3YAR3I3S.js";
7
7
  import {
8
8
  createDraft14Codec
9
- } from "./chunk-DORX55MO.js";
9
+ } from "./chunk-XQGEOFGE.js";
10
10
  import {
11
11
  createDraft15Codec
12
- } from "./chunk-MD2OA7SE.js";
12
+ } from "./chunk-BV6MITLO.js";
13
13
  import {
14
14
  createDraft16Codec
15
- } from "./chunk-HEO2JG6C.js";
15
+ } from "./chunk-TMXRYOBS.js";
16
16
  import {
17
17
  createDraft17Codec
18
- } from "./chunk-I55J3KXD.js";
18
+ } from "./chunk-NH4L6NIY.js";
19
19
  import {
20
20
  createDraft10Codec
21
21
  } from "./chunk-N2V7Z37J.js";
@@ -24,7 +24,7 @@ import {
24
24
  } from "./chunk-JKMYAUGY.js";
25
25
  import {
26
26
  createDraft12Codec
27
- } from "./chunk-KNLPX3YA.js";
27
+ } from "./chunk-H6DTAIWM.js";
28
28
  import {
29
29
  createDraft13Codec
30
30
  } from "./chunk-23KRITJ6.js";
@@ -41,6 +41,54 @@ import {
41
41
  DecodeError
42
42
  } from "./chunk-KLPWJU6W.js";
43
43
 
44
+ // src/core/accessors.ts
45
+ function pick(msg, names) {
46
+ for (const name of names) {
47
+ const value = msg[name];
48
+ if (value != null) return value;
49
+ }
50
+ return void 0;
51
+ }
52
+ function bigintOf(value) {
53
+ if (value == null) return void 0;
54
+ if (typeof value === "bigint") return value;
55
+ if (typeof value === "number" || typeof value === "string") {
56
+ try {
57
+ return BigInt(value);
58
+ } catch {
59
+ return void 0;
60
+ }
61
+ }
62
+ return void 0;
63
+ }
64
+ function requestIdOf(msg) {
65
+ return bigintOf(pick(msg, ["request_id", "requestId", "subscribe_id", "subscribeId"]));
66
+ }
67
+ function trackAliasOf(msg) {
68
+ return bigintOf(pick(msg, ["track_alias", "trackAlias"]));
69
+ }
70
+ function trackOf(msg) {
71
+ const source = pick(msg, ["standalone"]) ?? msg;
72
+ const name = pick(source, ["track_name", "trackName"]);
73
+ if (typeof name !== "string") return void 0;
74
+ const namespace = pick(source, ["track_namespace", "trackNamespace"]);
75
+ return {
76
+ namespace: Array.isArray(namespace) ? namespace.map(String) : [],
77
+ name
78
+ };
79
+ }
80
+ function joiningRequestIdOf(msg) {
81
+ const source = pick(msg, ["joining"]) ?? msg;
82
+ return bigintOf(
83
+ pick(source, [
84
+ "joining_request_id",
85
+ "joiningRequestId",
86
+ "joining_subscribe_id",
87
+ "joiningSubscribeId"
88
+ ])
89
+ );
90
+ }
91
+
44
92
  // src/index.ts
45
93
  var DRAFT_VERSIONS = {
46
94
  "07": 0xff000007n,
@@ -99,5 +147,9 @@ Supported draft values: ${Object.keys(DRAFT_VERSIONS).join(", ")}`
99
147
  export {
100
148
  DRAFT_VERSIONS,
101
149
  DecodeError,
102
- createCodec
150
+ createCodec,
151
+ joiningRequestIdOf,
152
+ requestIdOf,
153
+ trackAliasOf,
154
+ trackOf
103
155
  };
@@ -8,7 +8,7 @@ import { D as Draft10Message, a as Draft10MessageType } from './types-0IWbU9Z_.c
8
8
  import { D as Draft11Message, a as Draft11MessageType } from './types-Ck45Ex5S.cjs';
9
9
  import { D as Draft12Message, a as Draft12MessageType } from './types-Bio-91Rt.cjs';
10
10
  import { D as Draft13Message, a as Draft13MessageType } from './types-CXlSn0I9.cjs';
11
- import { D as Draft14Message, a as Draft14MessageType } from './types-DjmrHp_s.cjs';
11
+ import { D as Draft14Message, a as Draft14MessageType } from './types-HI_bbXka.cjs';
12
12
  import { D as Draft15Message, a as Draft15MessageType } from './types-CPaYBsMG.cjs';
13
13
  import { D as Draft16Message, a as Draft16MessageType } from './types-Cm9fjeYC.cjs';
14
14
  import { D as Draft17Message, a as Draft17MessageType } from './types-D9eiB_9A.cjs';
package/dist/session.d.ts CHANGED
@@ -8,7 +8,7 @@ import { D as Draft10Message, a as Draft10MessageType } from './types-0IWbU9Z_.j
8
8
  import { D as Draft11Message, a as Draft11MessageType } from './types-Ck45Ex5S.js';
9
9
  import { D as Draft12Message, a as Draft12MessageType } from './types-Bio-91Rt.js';
10
10
  import { D as Draft13Message, a as Draft13MessageType } from './types-CXlSn0I9.js';
11
- import { D as Draft14Message, a as Draft14MessageType } from './types-DjmrHp_s.js';
11
+ import { D as Draft14Message, a as Draft14MessageType } from './types-HI_bbXka.js';
12
12
  import { D as Draft15Message, a as Draft15MessageType } from './types-CPaYBsMG.js';
13
13
  import { D as Draft16Message, a as Draft16MessageType } from './types-Cm9fjeYC.js';
14
14
  import { D as Draft17Message, a as Draft17MessageType } from './types-D9eiB_9A.js';
@@ -63,8 +63,8 @@ interface Draft14Subscribe extends Draft14BaseMessage {
63
63
  readonly request_id: bigint;
64
64
  readonly track_namespace: string[];
65
65
  readonly track_name: string;
66
- readonly subscriber_priority: bigint;
67
- readonly group_order: bigint;
66
+ readonly subscriber_priority: number;
67
+ readonly group_order: number;
68
68
  readonly forward: bigint;
69
69
  readonly filter_type: bigint;
70
70
  readonly start_group?: bigint;
@@ -77,7 +77,7 @@ interface Draft14SubscribeOk extends Draft14BaseMessage {
77
77
  readonly request_id: bigint;
78
78
  readonly track_alias: bigint;
79
79
  readonly expires: bigint;
80
- readonly group_order: bigint;
80
+ readonly group_order: number;
81
81
  readonly content_exists: bigint;
82
82
  readonly largest_location?: Location;
83
83
  readonly parameters: Draft14Params;
@@ -89,7 +89,7 @@ interface Draft14SubscribeUpdate extends Draft14BaseMessage {
89
89
  readonly start_group: bigint;
90
90
  readonly start_object: bigint;
91
91
  readonly end_group: bigint;
92
- readonly subscriber_priority: bigint;
92
+ readonly subscriber_priority: number;
93
93
  readonly forward: bigint;
94
94
  readonly parameters: Draft14Params;
95
95
  }
@@ -109,7 +109,7 @@ interface Draft14Publish extends Draft14BaseMessage {
109
109
  readonly track_namespace: string[];
110
110
  readonly track_name: string;
111
111
  readonly track_alias: bigint;
112
- readonly group_order: bigint;
112
+ readonly group_order: number;
113
113
  readonly content_exists: bigint;
114
114
  readonly largest_location?: Location;
115
115
  readonly forward: bigint;
@@ -119,8 +119,8 @@ interface Draft14PublishOk extends Draft14BaseMessage {
119
119
  readonly type: 'publish_ok';
120
120
  readonly request_id: bigint;
121
121
  readonly forward: bigint;
122
- readonly subscriber_priority: bigint;
123
- readonly group_order: bigint;
122
+ readonly subscriber_priority: number;
123
+ readonly group_order: number;
124
124
  readonly filter_type: bigint;
125
125
  readonly start_group?: bigint;
126
126
  readonly start_object?: bigint;
@@ -149,7 +149,6 @@ interface Draft14PublishNamespace extends Draft14BaseMessage {
149
149
  interface Draft14PublishNamespaceOk extends Draft14BaseMessage {
150
150
  readonly type: 'publish_namespace_ok';
151
151
  readonly request_id: bigint;
152
- readonly parameters: Draft14Params;
153
152
  }
154
153
  interface Draft14PublishNamespaceError extends Draft14BaseMessage {
155
154
  readonly type: 'publish_namespace_error';
@@ -176,7 +175,6 @@ interface Draft14SubscribeNamespace extends Draft14BaseMessage {
176
175
  interface Draft14SubscribeNamespaceOk extends Draft14BaseMessage {
177
176
  readonly type: 'subscribe_namespace_ok';
178
177
  readonly request_id: bigint;
179
- readonly parameters: Draft14Params;
180
178
  }
181
179
  interface Draft14SubscribeNamespaceError extends Draft14BaseMessage {
182
180
  readonly type: 'subscribe_namespace_error';
@@ -188,26 +186,34 @@ interface Draft14UnsubscribeNamespace extends Draft14BaseMessage {
188
186
  readonly type: 'unsubscribe_namespace';
189
187
  readonly track_namespace_prefix: string[];
190
188
  }
189
+ interface StandaloneFetch {
190
+ readonly track_namespace: string[];
191
+ readonly track_name: string;
192
+ readonly start_group: bigint;
193
+ readonly start_object: bigint;
194
+ readonly end_group: bigint;
195
+ readonly end_object: bigint;
196
+ }
197
+ interface JoiningFetch {
198
+ readonly joining_request_id: bigint;
199
+ readonly joining_start: bigint;
200
+ }
191
201
  interface Draft14Fetch extends Draft14BaseMessage {
192
202
  readonly type: 'fetch';
193
203
  readonly request_id: bigint;
194
- readonly subscriber_priority: bigint;
195
- readonly group_order: bigint;
204
+ readonly subscriber_priority: number;
205
+ readonly group_order: number;
196
206
  readonly fetch_type: bigint;
197
- readonly track_namespace?: string[];
198
- readonly track_name?: string;
199
- readonly start_group?: bigint;
200
- readonly start_object?: bigint;
201
- readonly end_group?: bigint;
202
- readonly end_object?: bigint;
203
- readonly joining_request_id?: bigint;
204
- readonly joining_start?: bigint;
207
+ /** Present when fetch_type = 1 */
208
+ readonly standalone?: StandaloneFetch;
209
+ /** Present when fetch_type = 2 or 3 */
210
+ readonly joining?: JoiningFetch;
205
211
  readonly parameters: Draft14Params;
206
212
  }
207
213
  interface Draft14FetchOk extends Draft14BaseMessage {
208
214
  readonly type: 'fetch_ok';
209
215
  readonly request_id: bigint;
210
- readonly group_order: bigint;
216
+ readonly group_order: number;
211
217
  readonly end_of_track: bigint;
212
218
  readonly end_location: Location;
213
219
  readonly parameters: Draft14Params;
@@ -227,8 +233,8 @@ interface Draft14TrackStatus extends Draft14BaseMessage {
227
233
  readonly request_id: bigint;
228
234
  readonly track_namespace: string[];
229
235
  readonly track_name: string;
230
- readonly subscriber_priority: bigint;
231
- readonly group_order: bigint;
236
+ readonly subscriber_priority: number;
237
+ readonly group_order: number;
232
238
  readonly forward: bigint;
233
239
  readonly filter_type: bigint;
234
240
  readonly start_group?: bigint;
@@ -240,7 +246,7 @@ interface Draft14TrackStatusOk extends Draft14BaseMessage {
240
246
  readonly request_id: bigint;
241
247
  readonly track_alias: bigint;
242
248
  readonly expires: bigint;
243
- readonly group_order: bigint;
249
+ readonly group_order: number;
244
250
  readonly content_exists: bigint;
245
251
  readonly largest_location?: Location;
246
252
  readonly parameters: Draft14Params;
@@ -63,8 +63,8 @@ interface Draft14Subscribe extends Draft14BaseMessage {
63
63
  readonly request_id: bigint;
64
64
  readonly track_namespace: string[];
65
65
  readonly track_name: string;
66
- readonly subscriber_priority: bigint;
67
- readonly group_order: bigint;
66
+ readonly subscriber_priority: number;
67
+ readonly group_order: number;
68
68
  readonly forward: bigint;
69
69
  readonly filter_type: bigint;
70
70
  readonly start_group?: bigint;
@@ -77,7 +77,7 @@ interface Draft14SubscribeOk extends Draft14BaseMessage {
77
77
  readonly request_id: bigint;
78
78
  readonly track_alias: bigint;
79
79
  readonly expires: bigint;
80
- readonly group_order: bigint;
80
+ readonly group_order: number;
81
81
  readonly content_exists: bigint;
82
82
  readonly largest_location?: Location;
83
83
  readonly parameters: Draft14Params;
@@ -89,7 +89,7 @@ interface Draft14SubscribeUpdate extends Draft14BaseMessage {
89
89
  readonly start_group: bigint;
90
90
  readonly start_object: bigint;
91
91
  readonly end_group: bigint;
92
- readonly subscriber_priority: bigint;
92
+ readonly subscriber_priority: number;
93
93
  readonly forward: bigint;
94
94
  readonly parameters: Draft14Params;
95
95
  }
@@ -109,7 +109,7 @@ interface Draft14Publish extends Draft14BaseMessage {
109
109
  readonly track_namespace: string[];
110
110
  readonly track_name: string;
111
111
  readonly track_alias: bigint;
112
- readonly group_order: bigint;
112
+ readonly group_order: number;
113
113
  readonly content_exists: bigint;
114
114
  readonly largest_location?: Location;
115
115
  readonly forward: bigint;
@@ -119,8 +119,8 @@ interface Draft14PublishOk extends Draft14BaseMessage {
119
119
  readonly type: 'publish_ok';
120
120
  readonly request_id: bigint;
121
121
  readonly forward: bigint;
122
- readonly subscriber_priority: bigint;
123
- readonly group_order: bigint;
122
+ readonly subscriber_priority: number;
123
+ readonly group_order: number;
124
124
  readonly filter_type: bigint;
125
125
  readonly start_group?: bigint;
126
126
  readonly start_object?: bigint;
@@ -149,7 +149,6 @@ interface Draft14PublishNamespace extends Draft14BaseMessage {
149
149
  interface Draft14PublishNamespaceOk extends Draft14BaseMessage {
150
150
  readonly type: 'publish_namespace_ok';
151
151
  readonly request_id: bigint;
152
- readonly parameters: Draft14Params;
153
152
  }
154
153
  interface Draft14PublishNamespaceError extends Draft14BaseMessage {
155
154
  readonly type: 'publish_namespace_error';
@@ -176,7 +175,6 @@ interface Draft14SubscribeNamespace extends Draft14BaseMessage {
176
175
  interface Draft14SubscribeNamespaceOk extends Draft14BaseMessage {
177
176
  readonly type: 'subscribe_namespace_ok';
178
177
  readonly request_id: bigint;
179
- readonly parameters: Draft14Params;
180
178
  }
181
179
  interface Draft14SubscribeNamespaceError extends Draft14BaseMessage {
182
180
  readonly type: 'subscribe_namespace_error';
@@ -188,26 +186,34 @@ interface Draft14UnsubscribeNamespace extends Draft14BaseMessage {
188
186
  readonly type: 'unsubscribe_namespace';
189
187
  readonly track_namespace_prefix: string[];
190
188
  }
189
+ interface StandaloneFetch {
190
+ readonly track_namespace: string[];
191
+ readonly track_name: string;
192
+ readonly start_group: bigint;
193
+ readonly start_object: bigint;
194
+ readonly end_group: bigint;
195
+ readonly end_object: bigint;
196
+ }
197
+ interface JoiningFetch {
198
+ readonly joining_request_id: bigint;
199
+ readonly joining_start: bigint;
200
+ }
191
201
  interface Draft14Fetch extends Draft14BaseMessage {
192
202
  readonly type: 'fetch';
193
203
  readonly request_id: bigint;
194
- readonly subscriber_priority: bigint;
195
- readonly group_order: bigint;
204
+ readonly subscriber_priority: number;
205
+ readonly group_order: number;
196
206
  readonly fetch_type: bigint;
197
- readonly track_namespace?: string[];
198
- readonly track_name?: string;
199
- readonly start_group?: bigint;
200
- readonly start_object?: bigint;
201
- readonly end_group?: bigint;
202
- readonly end_object?: bigint;
203
- readonly joining_request_id?: bigint;
204
- readonly joining_start?: bigint;
207
+ /** Present when fetch_type = 1 */
208
+ readonly standalone?: StandaloneFetch;
209
+ /** Present when fetch_type = 2 or 3 */
210
+ readonly joining?: JoiningFetch;
205
211
  readonly parameters: Draft14Params;
206
212
  }
207
213
  interface Draft14FetchOk extends Draft14BaseMessage {
208
214
  readonly type: 'fetch_ok';
209
215
  readonly request_id: bigint;
210
- readonly group_order: bigint;
216
+ readonly group_order: number;
211
217
  readonly end_of_track: bigint;
212
218
  readonly end_location: Location;
213
219
  readonly parameters: Draft14Params;
@@ -227,8 +233,8 @@ interface Draft14TrackStatus extends Draft14BaseMessage {
227
233
  readonly request_id: bigint;
228
234
  readonly track_namespace: string[];
229
235
  readonly track_name: string;
230
- readonly subscriber_priority: bigint;
231
- readonly group_order: bigint;
236
+ readonly subscriber_priority: number;
237
+ readonly group_order: number;
232
238
  readonly forward: bigint;
233
239
  readonly filter_type: bigint;
234
240
  readonly start_group?: bigint;
@@ -240,7 +246,7 @@ interface Draft14TrackStatusOk extends Draft14BaseMessage {
240
246
  readonly request_id: bigint;
241
247
  readonly track_alias: bigint;
242
248
  readonly expires: bigint;
243
- readonly group_order: bigint;
249
+ readonly group_order: number;
244
250
  readonly content_exists: bigint;
245
251
  readonly largest_location?: Location;
246
252
  readonly parameters: Draft14Params;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moqtap/codec",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "MoQT wire-format codec and session state machine — multi-draft, zero dependencies",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -208,7 +208,7 @@
208
208
  "test": "vitest run"
209
209
  },
210
210
  "devDependencies": {
211
- "@moqtap/test-vectors": "^0.11.0",
211
+ "@moqtap/test-vectors": "^0.12.1",
212
212
  "tsup": "^8.0.0",
213
213
  "typescript": "^5.5.0",
214
214
  "vitest": "^3.0.0"
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Draft-agnostic readers for decoded control messages.
3
+ *
4
+ * The codec keeps one message model per draft, because the drafts genuinely
5
+ * disagree: a track alias travels in SUBSCRIBE through draft-11 and in
6
+ * SUBSCRIBE_OK from draft-12; a request was a `subscribe_id` through draft-10
7
+ * and a `request_id` after it; a joining FETCH points at a
8
+ * `joining_subscribe_id` through draft-13 and a `joining_request_id` after it.
9
+ * Faithfulness to each draft is the point of the codec.
10
+ *
11
+ * What consumers usually want is the answer, not the archaeology — and every
12
+ * one of them that has asked has written the same field-spelling ladder by
13
+ * hand. These accessors are that ladder, written once.
14
+ *
15
+ * Each returns `undefined` when the message does not carry the field, which
16
+ * includes the cases where the draft has not assigned it yet: `trackAliasOf`
17
+ * on a draft-14 SUBSCRIBE is `undefined` because the publisher has not chosen
18
+ * the alias until SUBSCRIBE_OK.
19
+ *
20
+ * Both snake_case (what this codec emits) and camelCase spellings are read, so
21
+ * these also work on messages recovered from qlog-style traces written by
22
+ * other tools.
23
+ */
24
+
25
+ /** Any decoded control message, from any draft. */
26
+ export type AnyMessage = Readonly<Record<string, unknown>>
27
+
28
+ /** A track's full identity: the namespace tuple plus the name within it. */
29
+ export interface TrackIdentity {
30
+ readonly namespace: string[]
31
+ readonly name: string
32
+ }
33
+
34
+ /** First defined value among several spellings of one field. */
35
+ function pick(msg: AnyMessage, names: readonly string[]): unknown {
36
+ for (const name of names) {
37
+ const value = msg[name]
38
+ if (value != null) return value
39
+ }
40
+ return undefined
41
+ }
42
+
43
+ function bigintOf(value: unknown): bigint | undefined {
44
+ if (value == null) return undefined
45
+ if (typeof value === 'bigint') return value
46
+ if (typeof value === 'number' || typeof value === 'string') {
47
+ try {
48
+ return BigInt(value)
49
+ } catch {
50
+ return undefined
51
+ }
52
+ }
53
+ return undefined
54
+ }
55
+
56
+ /**
57
+ * The request this message belongs to.
58
+ *
59
+ * Named `subscribe_id` through draft-10 and `request_id` from draft-11, when
60
+ * the id space widened to cover every request type rather than subscriptions
61
+ * alone.
62
+ *
63
+ * Returns `undefined` for draft-17+ responses, which carry no id at all: each
64
+ * request there has its own bidirectional stream, and that stream — not a
65
+ * field — is what ties a response to its request.
66
+ */
67
+ export function requestIdOf(msg: AnyMessage): bigint | undefined {
68
+ return bigintOf(pick(msg, ['request_id', 'requestId', 'subscribe_id', 'subscribeId']))
69
+ }
70
+
71
+ /**
72
+ * The track alias this message assigns or refers to, if any.
73
+ *
74
+ * Which message carries it moved: SUBSCRIBE through draft-11, then SUBSCRIBE_OK
75
+ * from draft-12, when assigning the alias became the publisher's job. PUBLISH
76
+ * carries it alongside the track name in every draft that has PUBLISH.
77
+ */
78
+ export function trackAliasOf(msg: AnyMessage): bigint | undefined {
79
+ return bigintOf(pick(msg, ['track_alias', 'trackAlias']))
80
+ }
81
+
82
+ /**
83
+ * The track this message names, or `undefined` if it names none.
84
+ *
85
+ * Covers SUBSCRIBE, PUBLISH, TRACK_STATUS and a standalone FETCH — whose track
86
+ * sits under `standalone` in most drafts and flat in draft-07, which predates
87
+ * joining fetch. A joining FETCH names no track of its own; use
88
+ * {@link joiningRequestIdOf} to find the request whose track it continues.
89
+ *
90
+ * PUBLISH_NAMESPACE and its relatives are namespace-only and yield
91
+ * `undefined`: a namespace is not a track.
92
+ */
93
+ export function trackOf(msg: AnyMessage): TrackIdentity | undefined {
94
+ const source = (pick(msg, ['standalone']) ?? msg) as AnyMessage
95
+ const name = pick(source, ['track_name', 'trackName'])
96
+ if (typeof name !== 'string') return undefined
97
+ const namespace = pick(source, ['track_namespace', 'trackNamespace'])
98
+ return {
99
+ namespace: Array.isArray(namespace) ? namespace.map(String) : [],
100
+ name,
101
+ }
102
+ }
103
+
104
+ /**
105
+ * For a joining FETCH, the request whose track it continues.
106
+ *
107
+ * Spelled `joining_subscribe_id` through draft-13 and `joining_request_id`
108
+ * after it, nested under `joining` in every draft that has one.
109
+ */
110
+ export function joiningRequestIdOf(msg: AnyMessage): bigint | undefined {
111
+ const source = (pick(msg, ['joining']) ?? msg) as AnyMessage
112
+ return bigintOf(
113
+ pick(source, [
114
+ 'joining_request_id',
115
+ 'joiningRequestId',
116
+ 'joining_subscribe_id',
117
+ 'joiningSubscribeId',
118
+ ]),
119
+ )
120
+ }
@@ -56,7 +56,7 @@ export function encodeDatagram(dg: DatagramObject): Uint8Array {
56
56
  w.writeVarInt(extData.byteLength)
57
57
  if (extData.byteLength > 0) w.writeBytes(extData)
58
58
  }
59
- const isStatus = dg.streamTypeId === 0x02 || dg.streamTypeId === 0x03
59
+ const isStatus = dg.streamTypeId >= 0x04
60
60
  if (isStatus) {
61
61
  w.writeVarInt(dg.objectStatus ?? 0n)
62
62
  } else {
@@ -182,18 +182,26 @@ export function decodeDatagram(bytes: Uint8Array): DecodeResult<DatagramObject>
182
182
  try {
183
183
  const r = new BufferReader(bytes)
184
184
  const streamTypeId = Number(r.readVarInt())
185
- if (streamTypeId < 0x00 || streamTypeId > 0x03) {
185
+ if (streamTypeId < 0x00 || streamTypeId > 0x05) {
186
186
  return {
187
187
  ok: false,
188
188
  error: new DecodeError(
189
189
  'CONSTRAINT_VIOLATION',
190
- `Expected datagram type 0x00-0x03, got 0x${streamTypeId.toString(16)}`,
190
+ `Expected datagram type 0x00-0x05, got 0x${streamTypeId.toString(16)}`,
191
191
  0,
192
192
  ),
193
193
  }
194
194
  }
195
195
  const extensionsPresent = (streamTypeId & 0x01) !== 0
196
- const isStatus = streamTypeId === 0x02 || streamTypeId === 0x03
196
+ // Draft-12 states its datagram types twice and the two disagree. Table 12
197
+ // gives OBJECT_DATAGRAM four values and puts the End of Group marker on
198
+ // 0x02/0x03; the OBJECT_DATAGRAM_STATUS paragraph still says "0x02 to
199
+ // 0x03", which is the sentence draft-11 wrote before an End of Group
200
+ // marker existed. Both cannot hold, and the table is the half that
201
+ // changed. Draft-13 repeats the contradiction verbatim and draft-14
202
+ // resolves it the same way: payload below 0x04, status at and above it.
203
+ const endOfGroup = (streamTypeId & 0x02) !== 0 && streamTypeId < 0x04
204
+ const isStatus = streamTypeId >= 0x04
197
205
  const trackAlias = r.readVarInt()
198
206
  const groupId = r.readVarInt()
199
207
  const objectId = r.readVarInt()
@@ -225,6 +233,7 @@ export function decodeDatagram(bytes: Uint8Array): DecodeResult<DatagramObject>
225
233
  payloadLength: payload.byteLength,
226
234
  payload,
227
235
  }
236
+ if (endOfGroup) (result as unknown as Record<string, unknown>).endOfGroup = true
228
237
  if (extensionHeadersLength !== undefined)
229
238
  (result as unknown as Record<string, unknown>).extensionHeadersLength = extensionHeadersLength
230
239
  if (extensionData !== undefined)