@moqtap/trace 0.1.3 → 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/SPEC.md CHANGED
@@ -117,12 +117,27 @@ Short key names are used intentionally — traces can contain hundreds of thousa
117
117
 
118
118
  #### Event 0: Control Message
119
119
 
120
- | Key | Type | Detail Level | Description |
121
- | ------- | ----------- | ------------ | ------------------------------------------------- |
122
- | `"d"` | integer | `control`+ | Direction: `0` = sent (tx), `1` = received (rx) |
123
- | `"mt"` | integer | `control`+ | Wire message type ID (e.g., `0x03` for SUBSCRIBE) |
124
- | `"msg"` | map | `control`+ | Decoded message fields (draft-specific structure) |
125
- | `"raw"` | byte string | `full` only | Raw wire bytes (including type and length prefix) |
120
+ | Key | Type | Detail Level | Description |
121
+ | ------- | ----------- | ------------ | ---------------------------------------------------------------- |
122
+ | `"d"` | integer | `control`+ | Direction: `0` = sent (tx), `1` = received (rx) |
123
+ | `"mt"` | integer | `control`+ | Wire message type ID (e.g., `0x03` for SUBSCRIBE) |
124
+ | `"msg"` | map | `control`+ | Decoded message fields (draft-specific structure) |
125
+ | `"sid"` | integer | `control`+ | QUIC stream ID the message travelled on. Optional (see below). |
126
+ | `"raw"` | byte string | `full` only | Raw wire bytes (including type and length prefix) |
127
+
128
+ `"sid"` is optional: a recorder that sits at the session level rather than the
129
+ stream level has no stream to report, and omitting the key says so. Readers
130
+ MUST distinguish an absent `"sid"` from stream `0`.
131
+
132
+ Recorders SHOULD write it. From draft-17 the control plane is no longer a
133
+ single stream — the session control stream became a pair of unidirectional
134
+ streams, and every request (SUBSCRIBE, PUBLISH, FETCH, …) gets its own
135
+ bidirectional stream. Responses on those streams carry no request ID, because
136
+ the stream itself is the correlation, so a trace without `"sid"` cannot tie a
137
+ SUBSCRIBE_OK back to its SUBSCRIBE, and everything derived from that pairing —
138
+ track names, aliases, request outcomes — is lost to the reader. Through
139
+ draft-16 all control messages share one stream and the field is merely
140
+ informative.
126
141
 
127
142
  #### Event 1: Stream Opened
128
143
 
@@ -215,6 +230,10 @@ Object header events and object payload events (event 4) for the same object sha
215
230
  - **Version 1** is defined by this document.
216
231
  - Readers MUST check the format version before parsing.
217
232
  - Unknown keys in header maps or event maps MUST be ignored (forward-compatible).
233
+ - Optional keys MAY be added to an existing event type without a version bump —
234
+ `"sid"` on event 0 was added this way. A reader must therefore treat any
235
+ optional key as absent-by-default rather than assuming files of a given
236
+ version all carry the same keys.
218
237
  - New event types (`"e"` values) MAY be added in future versions; readers SHOULD skip unknown event types.
219
238
  - The detail level in the header is informational — readers MUST handle missing fields gracefully regardless of declared level.
220
239
 
package/dist/index.cjs CHANGED
@@ -69,6 +69,7 @@ function eventToCbor(event) {
69
69
  base.d = event.direction;
70
70
  base.mt = event.messageType;
71
71
  base.msg = event.message;
72
+ if (event.streamId != null) base.sid = event.streamId;
72
73
  if (event.raw != null) base.raw = event.raw;
73
74
  break;
74
75
  }
@@ -139,6 +140,7 @@ function cborToEvent(obj) {
139
140
  direction: obj.d,
140
141
  messageType: Number(_nullishCoalesce(obj.mt, () => ( 0))),
141
142
  message: _nullishCoalesce(obj.msg, () => ( {})),
143
+ ...obj.sid != null ? { streamId: BigInt(obj.sid) } : {},
142
144
  ...obj.raw != null ? { raw: obj.raw } : {}
143
145
  };
144
146
  case "stream-opened":
package/dist/index.d.cts CHANGED
@@ -24,6 +24,15 @@ interface ControlMessageEvent extends BaseEvent {
24
24
  readonly messageType: number;
25
25
  readonly message: Record<string, unknown>;
26
26
  readonly raw?: Uint8Array;
27
+ /**
28
+ * QUIC stream the message travelled on.
29
+ *
30
+ * Optional, and only meaningful for a recorder that knows it. It matters
31
+ * from draft-17, where the control plane is no longer one stream: each
32
+ * request gets its own bidirectional stream and responses carry no request
33
+ * id, so the stream is the only thing tying a response to its request.
34
+ */
35
+ readonly streamId?: bigint;
27
36
  }
28
37
  interface StreamOpenedEvent extends BaseEvent {
29
38
  readonly type: 'stream-opened';
package/dist/index.d.ts CHANGED
@@ -24,6 +24,15 @@ interface ControlMessageEvent extends BaseEvent {
24
24
  readonly messageType: number;
25
25
  readonly message: Record<string, unknown>;
26
26
  readonly raw?: Uint8Array;
27
+ /**
28
+ * QUIC stream the message travelled on.
29
+ *
30
+ * Optional, and only meaningful for a recorder that knows it. It matters
31
+ * from draft-17, where the control plane is no longer one stream: each
32
+ * request gets its own bidirectional stream and responses carry no request
33
+ * id, so the stream is the only thing tying a response to its request.
34
+ */
35
+ readonly streamId?: bigint;
27
36
  }
28
37
  interface StreamOpenedEvent extends BaseEvent {
29
38
  readonly type: 'stream-opened';
package/dist/index.js CHANGED
@@ -69,6 +69,7 @@ function eventToCbor(event) {
69
69
  base.d = event.direction;
70
70
  base.mt = event.messageType;
71
71
  base.msg = event.message;
72
+ if (event.streamId != null) base.sid = event.streamId;
72
73
  if (event.raw != null) base.raw = event.raw;
73
74
  break;
74
75
  }
@@ -139,6 +140,7 @@ function cborToEvent(obj) {
139
140
  direction: obj.d,
140
141
  messageType: Number(obj.mt ?? 0),
141
142
  message: obj.msg ?? {},
143
+ ...obj.sid != null ? { streamId: BigInt(obj.sid) } : {},
142
144
  ...obj.raw != null ? { raw: obj.raw } : {}
143
145
  };
144
146
  case "stream-opened":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moqtap/trace",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "MoQT session trace format — record, serialize, and analyze .moqtrace files",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/binary.ts CHANGED
@@ -89,6 +89,7 @@ function eventToCbor(event: TraceEvent): Record<string, unknown> {
89
89
  base.d = event.direction
90
90
  base.mt = event.messageType
91
91
  base.msg = event.message
92
+ if (event.streamId != null) base.sid = event.streamId
92
93
  if (event.raw != null) base.raw = event.raw
93
94
  break
94
95
  }
@@ -164,6 +165,7 @@ function cborToEvent(obj: Record<string, unknown>): TraceEvent {
164
165
  direction: obj.d as 0 | 1,
165
166
  messageType: Number(obj.mt ?? 0),
166
167
  message: (obj.msg ?? {}) as Record<string, unknown>,
168
+ ...(obj.sid != null ? { streamId: BigInt(obj.sid as bigint | number) } : {}),
167
169
  ...(obj.raw != null ? { raw: obj.raw as Uint8Array } : {}),
168
170
  }
169
171
 
package/src/types.ts CHANGED
@@ -25,6 +25,15 @@ export interface ControlMessageEvent extends BaseEvent {
25
25
  readonly messageType: number
26
26
  readonly message: Record<string, unknown>
27
27
  readonly raw?: Uint8Array
28
+ /**
29
+ * QUIC stream the message travelled on.
30
+ *
31
+ * Optional, and only meaningful for a recorder that knows it. It matters
32
+ * from draft-17, where the control plane is no longer one stream: each
33
+ * request gets its own bidirectional stream and responses carry no request
34
+ * id, so the stream is the only thing tying a response to its request.
35
+ */
36
+ readonly streamId?: bigint
28
37
  }
29
38
 
30
39
  export interface StreamOpenedEvent extends BaseEvent {