@moqtap/trace 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.
package/dist/index.cjs CHANGED
@@ -4,14 +4,14 @@ var MAGIC = new Uint8Array([77, 79, 81, 84, 82, 65, 67, 69]);
4
4
  var FORMAT_VERSION = 1;
5
5
  var PREAMBLE_SIZE = 16;
6
6
  var EVENT_TYPE_TO_INT = {
7
- "control": 0,
7
+ control: 0,
8
8
  "stream-opened": 1,
9
9
  "stream-closed": 2,
10
10
  "object-header": 3,
11
11
  "object-payload": 4,
12
12
  "state-change": 5,
13
- "error": 6,
14
- "annotation": 7
13
+ error: 6,
14
+ annotation: 7
15
15
  };
16
16
  var INT_TO_EVENT_TYPE = {
17
17
  0: "control",
@@ -279,13 +279,30 @@ function createMoqtraceWriter(header) {
279
279
  };
280
280
  }
281
281
 
282
+ // src/json.ts
283
+ function traceToJSON(trace) {
284
+ return JSON.stringify(
285
+ trace,
286
+ (_key, value) => {
287
+ if (typeof value === "bigint") {
288
+ return `0x${value.toString(16)}`;
289
+ }
290
+ if (value instanceof Uint8Array) {
291
+ return Array.from(value).map((b) => b.toString(16).padStart(2, "0")).join("");
292
+ }
293
+ return value;
294
+ },
295
+ 2
296
+ );
297
+ }
298
+
282
299
  // src/recorder.ts
283
300
  var DETAIL_RANK = {
284
- "control": 0,
285
- "headers": 1,
301
+ control: 0,
302
+ headers: 1,
286
303
  "headers+sizes": 2,
287
304
  "headers+data": 3,
288
- "full": 4
305
+ full: 4
289
306
  };
290
307
  function createRecorder(options) {
291
308
  const detail = options.detail;
@@ -385,7 +402,7 @@ function createRecorder(options) {
385
402
  wrapSession,
386
403
  record: addEvent,
387
404
  recordStreamOpened(streamId, direction, streamType) {
388
- if (detailRank < DETAIL_RANK["headers"]) return;
405
+ if (detailRank < DETAIL_RANK.headers) return;
389
406
  addEvent({
390
407
  type: "stream-opened",
391
408
  seq: nextSeq(),
@@ -396,7 +413,7 @@ function createRecorder(options) {
396
413
  });
397
414
  },
398
415
  recordStreamClosed(streamId, errorCode = 0) {
399
- if (detailRank < DETAIL_RANK["headers"]) return;
416
+ if (detailRank < DETAIL_RANK.headers) return;
400
417
  addEvent({
401
418
  type: "stream-closed",
402
419
  seq: nextSeq(),
@@ -406,7 +423,7 @@ function createRecorder(options) {
406
423
  });
407
424
  },
408
425
  recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus) {
409
- if (detailRank < DETAIL_RANK["headers"]) return;
426
+ if (detailRank < DETAIL_RANK.headers) return;
410
427
  addEvent({
411
428
  type: "object-header",
412
429
  seq: nextSeq(),
@@ -471,19 +488,6 @@ function createRecorder(options) {
471
488
  };
472
489
  }
473
490
 
474
- // src/json.ts
475
- function traceToJSON(trace) {
476
- return JSON.stringify(trace, (_key, value) => {
477
- if (typeof value === "bigint") {
478
- return `0x${value.toString(16)}`;
479
- }
480
- if (value instanceof Uint8Array) {
481
- return Array.from(value).map((b) => b.toString(16).padStart(2, "0")).join("");
482
- }
483
- return value;
484
- }, 2);
485
- }
486
-
487
491
 
488
492
 
489
493
 
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { SessionState } from '@moqtap/codec/session';
2
2
 
3
- type DetailLevel = 'control' | 'headers' | 'headers+sizes' | 'headers+data' | 'full';
4
- type Perspective = 'client' | 'server' | 'observer';
3
+ type DetailLevel = "control" | "headers" | "headers+sizes" | "headers+data" | "full";
4
+ type Perspective = "client" | "server" | "observer";
5
5
  interface TraceHeader {
6
6
  readonly protocol: string;
7
7
  readonly perspective: Perspective;
@@ -19,25 +19,25 @@ interface BaseEvent {
19
19
  readonly timestamp: number;
20
20
  }
21
21
  interface ControlMessageEvent extends BaseEvent {
22
- readonly type: 'control';
22
+ readonly type: "control";
23
23
  readonly direction: 0 | 1;
24
24
  readonly messageType: number;
25
25
  readonly message: Record<string, unknown>;
26
26
  readonly raw?: Uint8Array;
27
27
  }
28
28
  interface StreamOpenedEvent extends BaseEvent {
29
- readonly type: 'stream-opened';
29
+ readonly type: "stream-opened";
30
30
  readonly streamId: bigint;
31
31
  readonly direction: 0 | 1;
32
32
  readonly streamType: 0 | 1 | 2;
33
33
  }
34
34
  interface StreamClosedEvent extends BaseEvent {
35
- readonly type: 'stream-closed';
35
+ readonly type: "stream-closed";
36
36
  readonly streamId: bigint;
37
37
  readonly errorCode: number;
38
38
  }
39
39
  interface ObjectHeaderEvent extends BaseEvent {
40
- readonly type: 'object-header';
40
+ readonly type: "object-header";
41
41
  readonly streamId: bigint;
42
42
  readonly groupId: bigint;
43
43
  readonly objectId: bigint;
@@ -45,7 +45,7 @@ interface ObjectHeaderEvent extends BaseEvent {
45
45
  readonly objectStatus: number;
46
46
  }
47
47
  interface ObjectPayloadEvent extends BaseEvent {
48
- readonly type: 'object-payload';
48
+ readonly type: "object-payload";
49
49
  readonly streamId: bigint;
50
50
  readonly groupId: bigint;
51
51
  readonly objectId: bigint;
@@ -53,17 +53,17 @@ interface ObjectPayloadEvent extends BaseEvent {
53
53
  readonly payload?: Uint8Array;
54
54
  }
55
55
  interface StateChangeEvent extends BaseEvent {
56
- readonly type: 'state-change';
56
+ readonly type: "state-change";
57
57
  readonly from: string;
58
58
  readonly to: string;
59
59
  }
60
60
  interface TraceErrorEvent extends BaseEvent {
61
- readonly type: 'error';
61
+ readonly type: "error";
62
62
  readonly errorCode: number;
63
63
  readonly reason: string;
64
64
  }
65
65
  interface AnnotationEvent extends BaseEvent {
66
- readonly type: 'annotation';
66
+ readonly type: "annotation";
67
67
  readonly label: string;
68
68
  readonly data: unknown;
69
69
  }
@@ -109,6 +109,16 @@ interface MoqtraceWriter {
109
109
  }
110
110
  declare function createMoqtraceWriter(header: TraceHeader): MoqtraceWriter;
111
111
 
112
+ /**
113
+ * Serialize a trace to human-readable JSON.
114
+ *
115
+ * Handles bigint → hex string and Uint8Array → hex string.
116
+ * This is a one-way export for human inspection and debugging.
117
+ * For lossless round-tripping, use the binary .moqtrace format
118
+ * (`writeMoqtrace` / `readMoqtrace`).
119
+ */
120
+ declare function traceToJSON(trace: Trace): string;
121
+
112
122
  interface TraceRecorder {
113
123
  /** Wrap a SessionState to auto-record control messages and state changes. */
114
124
  wrapSession(session: SessionState): SessionState;
@@ -133,14 +143,4 @@ interface TraceRecorder {
133
143
  }
134
144
  declare function createRecorder(options: RecorderOptions): TraceRecorder;
135
145
 
136
- /**
137
- * Serialize a trace to human-readable JSON.
138
- *
139
- * Handles bigint → hex string and Uint8Array → hex string.
140
- * This is a one-way export for human inspection and debugging.
141
- * For lossless round-tripping, use the binary .moqtrace format
142
- * (`writeMoqtrace` / `readMoqtrace`).
143
- */
144
- declare function traceToJSON(trace: Trace): string;
145
-
146
146
  export { type AnnotationEvent, type ControlMessageEvent, type DetailLevel, type MoqtraceWriter, type ObjectHeaderEvent, type ObjectPayloadEvent, type Perspective, type RecorderOptions, type StateChangeEvent, type StreamClosedEvent, type StreamOpenedEvent, type Trace, type TraceErrorEvent, type TraceEvent, type TraceHeader, type TraceRecorder, createMoqtraceWriter, createRecorder, readMoqtrace, readMoqtraceHeader, traceToJSON, writeMoqtrace };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { SessionState } from '@moqtap/codec/session';
2
2
 
3
- type DetailLevel = 'control' | 'headers' | 'headers+sizes' | 'headers+data' | 'full';
4
- type Perspective = 'client' | 'server' | 'observer';
3
+ type DetailLevel = "control" | "headers" | "headers+sizes" | "headers+data" | "full";
4
+ type Perspective = "client" | "server" | "observer";
5
5
  interface TraceHeader {
6
6
  readonly protocol: string;
7
7
  readonly perspective: Perspective;
@@ -19,25 +19,25 @@ interface BaseEvent {
19
19
  readonly timestamp: number;
20
20
  }
21
21
  interface ControlMessageEvent extends BaseEvent {
22
- readonly type: 'control';
22
+ readonly type: "control";
23
23
  readonly direction: 0 | 1;
24
24
  readonly messageType: number;
25
25
  readonly message: Record<string, unknown>;
26
26
  readonly raw?: Uint8Array;
27
27
  }
28
28
  interface StreamOpenedEvent extends BaseEvent {
29
- readonly type: 'stream-opened';
29
+ readonly type: "stream-opened";
30
30
  readonly streamId: bigint;
31
31
  readonly direction: 0 | 1;
32
32
  readonly streamType: 0 | 1 | 2;
33
33
  }
34
34
  interface StreamClosedEvent extends BaseEvent {
35
- readonly type: 'stream-closed';
35
+ readonly type: "stream-closed";
36
36
  readonly streamId: bigint;
37
37
  readonly errorCode: number;
38
38
  }
39
39
  interface ObjectHeaderEvent extends BaseEvent {
40
- readonly type: 'object-header';
40
+ readonly type: "object-header";
41
41
  readonly streamId: bigint;
42
42
  readonly groupId: bigint;
43
43
  readonly objectId: bigint;
@@ -45,7 +45,7 @@ interface ObjectHeaderEvent extends BaseEvent {
45
45
  readonly objectStatus: number;
46
46
  }
47
47
  interface ObjectPayloadEvent extends BaseEvent {
48
- readonly type: 'object-payload';
48
+ readonly type: "object-payload";
49
49
  readonly streamId: bigint;
50
50
  readonly groupId: bigint;
51
51
  readonly objectId: bigint;
@@ -53,17 +53,17 @@ interface ObjectPayloadEvent extends BaseEvent {
53
53
  readonly payload?: Uint8Array;
54
54
  }
55
55
  interface StateChangeEvent extends BaseEvent {
56
- readonly type: 'state-change';
56
+ readonly type: "state-change";
57
57
  readonly from: string;
58
58
  readonly to: string;
59
59
  }
60
60
  interface TraceErrorEvent extends BaseEvent {
61
- readonly type: 'error';
61
+ readonly type: "error";
62
62
  readonly errorCode: number;
63
63
  readonly reason: string;
64
64
  }
65
65
  interface AnnotationEvent extends BaseEvent {
66
- readonly type: 'annotation';
66
+ readonly type: "annotation";
67
67
  readonly label: string;
68
68
  readonly data: unknown;
69
69
  }
@@ -109,6 +109,16 @@ interface MoqtraceWriter {
109
109
  }
110
110
  declare function createMoqtraceWriter(header: TraceHeader): MoqtraceWriter;
111
111
 
112
+ /**
113
+ * Serialize a trace to human-readable JSON.
114
+ *
115
+ * Handles bigint → hex string and Uint8Array → hex string.
116
+ * This is a one-way export for human inspection and debugging.
117
+ * For lossless round-tripping, use the binary .moqtrace format
118
+ * (`writeMoqtrace` / `readMoqtrace`).
119
+ */
120
+ declare function traceToJSON(trace: Trace): string;
121
+
112
122
  interface TraceRecorder {
113
123
  /** Wrap a SessionState to auto-record control messages and state changes. */
114
124
  wrapSession(session: SessionState): SessionState;
@@ -133,14 +143,4 @@ interface TraceRecorder {
133
143
  }
134
144
  declare function createRecorder(options: RecorderOptions): TraceRecorder;
135
145
 
136
- /**
137
- * Serialize a trace to human-readable JSON.
138
- *
139
- * Handles bigint → hex string and Uint8Array → hex string.
140
- * This is a one-way export for human inspection and debugging.
141
- * For lossless round-tripping, use the binary .moqtrace format
142
- * (`writeMoqtrace` / `readMoqtrace`).
143
- */
144
- declare function traceToJSON(trace: Trace): string;
145
-
146
146
  export { type AnnotationEvent, type ControlMessageEvent, type DetailLevel, type MoqtraceWriter, type ObjectHeaderEvent, type ObjectPayloadEvent, type Perspective, type RecorderOptions, type StateChangeEvent, type StreamClosedEvent, type StreamOpenedEvent, type Trace, type TraceErrorEvent, type TraceEvent, type TraceHeader, type TraceRecorder, createMoqtraceWriter, createRecorder, readMoqtrace, readMoqtraceHeader, traceToJSON, writeMoqtrace };
package/dist/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  // src/binary.ts
2
- import { encode, decode, decodeMultiple } from "cbor-x";
2
+ import { decode, decodeMultiple, encode } from "cbor-x";
3
3
  var MAGIC = new Uint8Array([77, 79, 81, 84, 82, 65, 67, 69]);
4
4
  var FORMAT_VERSION = 1;
5
5
  var PREAMBLE_SIZE = 16;
6
6
  var EVENT_TYPE_TO_INT = {
7
- "control": 0,
7
+ control: 0,
8
8
  "stream-opened": 1,
9
9
  "stream-closed": 2,
10
10
  "object-header": 3,
11
11
  "object-payload": 4,
12
12
  "state-change": 5,
13
- "error": 6,
14
- "annotation": 7
13
+ error: 6,
14
+ annotation: 7
15
15
  };
16
16
  var INT_TO_EVENT_TYPE = {
17
17
  0: "control",
@@ -279,13 +279,30 @@ function createMoqtraceWriter(header) {
279
279
  };
280
280
  }
281
281
 
282
+ // src/json.ts
283
+ function traceToJSON(trace) {
284
+ return JSON.stringify(
285
+ trace,
286
+ (_key, value) => {
287
+ if (typeof value === "bigint") {
288
+ return `0x${value.toString(16)}`;
289
+ }
290
+ if (value instanceof Uint8Array) {
291
+ return Array.from(value).map((b) => b.toString(16).padStart(2, "0")).join("");
292
+ }
293
+ return value;
294
+ },
295
+ 2
296
+ );
297
+ }
298
+
282
299
  // src/recorder.ts
283
300
  var DETAIL_RANK = {
284
- "control": 0,
285
- "headers": 1,
301
+ control: 0,
302
+ headers: 1,
286
303
  "headers+sizes": 2,
287
304
  "headers+data": 3,
288
- "full": 4
305
+ full: 4
289
306
  };
290
307
  function createRecorder(options) {
291
308
  const detail = options.detail;
@@ -385,7 +402,7 @@ function createRecorder(options) {
385
402
  wrapSession,
386
403
  record: addEvent,
387
404
  recordStreamOpened(streamId, direction, streamType) {
388
- if (detailRank < DETAIL_RANK["headers"]) return;
405
+ if (detailRank < DETAIL_RANK.headers) return;
389
406
  addEvent({
390
407
  type: "stream-opened",
391
408
  seq: nextSeq(),
@@ -396,7 +413,7 @@ function createRecorder(options) {
396
413
  });
397
414
  },
398
415
  recordStreamClosed(streamId, errorCode = 0) {
399
- if (detailRank < DETAIL_RANK["headers"]) return;
416
+ if (detailRank < DETAIL_RANK.headers) return;
400
417
  addEvent({
401
418
  type: "stream-closed",
402
419
  seq: nextSeq(),
@@ -406,7 +423,7 @@ function createRecorder(options) {
406
423
  });
407
424
  },
408
425
  recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus) {
409
- if (detailRank < DETAIL_RANK["headers"]) return;
426
+ if (detailRank < DETAIL_RANK.headers) return;
410
427
  addEvent({
411
428
  type: "object-header",
412
429
  seq: nextSeq(),
@@ -470,19 +487,6 @@ function createRecorder(options) {
470
487
  }
471
488
  };
472
489
  }
473
-
474
- // src/json.ts
475
- function traceToJSON(trace) {
476
- return JSON.stringify(trace, (_key, value) => {
477
- if (typeof value === "bigint") {
478
- return `0x${value.toString(16)}`;
479
- }
480
- if (value instanceof Uint8Array) {
481
- return Array.from(value).map((b) => b.toString(16).padStart(2, "0")).join("");
482
- }
483
- return value;
484
- }, 2);
485
- }
486
490
  export {
487
491
  createMoqtraceWriter,
488
492
  createRecorder,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moqtap/trace",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "MoQT session trace format — record, serialize, and analyze .moqtrace files",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -32,7 +32,7 @@
32
32
  ".": {
33
33
  "bun": "./src/index.ts",
34
34
  "types": "./dist/index.d.ts",
35
- "import": "./dist/index.mjs",
35
+ "import": "./dist/index.js",
36
36
  "require": "./dist/index.cjs"
37
37
  }
38
38
  },
package/src/binary.ts CHANGED
@@ -1,37 +1,31 @@
1
- import { encode, decode, decodeMultiple } from 'cbor-x';
2
- import type {
3
- Trace,
4
- TraceHeader,
5
- TraceEvent,
6
- DetailLevel,
7
- Perspective,
8
- } from './types.js';
1
+ import { decode, decodeMultiple, encode } from "cbor-x";
2
+ import type { DetailLevel, Perspective, Trace, TraceEvent, TraceHeader } from "./types.js";
9
3
 
10
4
  const MAGIC = new Uint8Array([0x4d, 0x4f, 0x51, 0x54, 0x52, 0x41, 0x43, 0x45]); // "MOQTRACE"
11
5
  const FORMAT_VERSION = 1;
12
6
  const PREAMBLE_SIZE = 16; // 8 magic + 4 version + 4 header length
13
7
 
14
8
  // Event type string ↔ integer mapping
15
- const EVENT_TYPE_TO_INT: Record<TraceEvent['type'], number> = {
16
- 'control': 0,
17
- 'stream-opened': 1,
18
- 'stream-closed': 2,
19
- 'object-header': 3,
20
- 'object-payload': 4,
21
- 'state-change': 5,
22
- 'error': 6,
23
- 'annotation': 7,
9
+ const EVENT_TYPE_TO_INT: Record<TraceEvent["type"], number> = {
10
+ control: 0,
11
+ "stream-opened": 1,
12
+ "stream-closed": 2,
13
+ "object-header": 3,
14
+ "object-payload": 4,
15
+ "state-change": 5,
16
+ error: 6,
17
+ annotation: 7,
24
18
  };
25
19
 
26
- const INT_TO_EVENT_TYPE: Record<number, TraceEvent['type']> = {
27
- 0: 'control',
28
- 1: 'stream-opened',
29
- 2: 'stream-closed',
30
- 3: 'object-header',
31
- 4: 'object-payload',
32
- 5: 'state-change',
33
- 6: 'error',
34
- 7: 'annotation',
20
+ const INT_TO_EVENT_TYPE: Record<number, TraceEvent["type"]> = {
21
+ 0: "control",
22
+ 1: "stream-opened",
23
+ 2: "stream-closed",
24
+ 3: "object-header",
25
+ 4: "object-payload",
26
+ 5: "state-change",
27
+ 6: "error",
28
+ 7: "annotation",
35
29
  };
36
30
 
37
31
  // --- Header serialization ---
@@ -77,25 +71,25 @@ function eventToCbor(event: TraceEvent): Record<string, unknown> {
77
71
  };
78
72
 
79
73
  switch (event.type) {
80
- case 'control': {
74
+ case "control": {
81
75
  base.d = event.direction;
82
76
  base.mt = event.messageType;
83
77
  base.msg = event.message;
84
78
  if (event.raw != null) base.raw = event.raw;
85
79
  break;
86
80
  }
87
- case 'stream-opened': {
81
+ case "stream-opened": {
88
82
  base.sid = event.streamId;
89
83
  base.d = event.direction;
90
84
  base.st = event.streamType;
91
85
  break;
92
86
  }
93
- case 'stream-closed': {
87
+ case "stream-closed": {
94
88
  base.sid = event.streamId;
95
89
  base.ec = event.errorCode;
96
90
  break;
97
91
  }
98
- case 'object-header': {
92
+ case "object-header": {
99
93
  base.sid = event.streamId;
100
94
  base.g = event.groupId;
101
95
  base.o = event.objectId;
@@ -103,7 +97,7 @@ function eventToCbor(event: TraceEvent): Record<string, unknown> {
103
97
  base.os = event.objectStatus;
104
98
  break;
105
99
  }
106
- case 'object-payload': {
100
+ case "object-payload": {
107
101
  base.sid = event.streamId;
108
102
  base.g = event.groupId;
109
103
  base.o = event.objectId;
@@ -111,17 +105,17 @@ function eventToCbor(event: TraceEvent): Record<string, unknown> {
111
105
  if (event.payload != null) base.pl = event.payload;
112
106
  break;
113
107
  }
114
- case 'state-change': {
108
+ case "state-change": {
115
109
  base.from = event.from;
116
110
  base.to = event.to;
117
111
  break;
118
112
  }
119
- case 'error': {
113
+ case "error": {
120
114
  base.ec = event.errorCode;
121
115
  base.reason = event.reason;
122
116
  break;
123
117
  }
124
- case 'annotation': {
118
+ case "annotation": {
125
119
  base.label = event.label;
126
120
  if (event.data !== undefined) base.data = event.data;
127
121
  break;
@@ -136,7 +130,7 @@ function cborToEvent(obj: Record<string, unknown>): TraceEvent {
136
130
  if (eventType == null) {
137
131
  // Unknown event type — skip per spec (return annotation as fallback)
138
132
  return {
139
- type: 'annotation',
133
+ type: "annotation",
140
134
  seq: Number(obj.n ?? 0),
141
135
  timestamp: Number(obj.t ?? 0),
142
136
  label: `unknown-event-${obj.e}`,
@@ -148,9 +142,9 @@ function cborToEvent(obj: Record<string, unknown>): TraceEvent {
148
142
  const timestamp = Number(obj.t ?? 0);
149
143
 
150
144
  switch (eventType) {
151
- case 'control':
145
+ case "control":
152
146
  return {
153
- type: 'control' as const,
147
+ type: "control" as const,
154
148
  seq,
155
149
  timestamp,
156
150
  direction: obj.d as 0 | 1,
@@ -159,9 +153,9 @@ function cborToEvent(obj: Record<string, unknown>): TraceEvent {
159
153
  ...(obj.raw != null ? { raw: obj.raw as Uint8Array } : {}),
160
154
  };
161
155
 
162
- case 'stream-opened':
156
+ case "stream-opened":
163
157
  return {
164
- type: 'stream-opened' as const,
158
+ type: "stream-opened" as const,
165
159
  seq,
166
160
  timestamp,
167
161
  streamId: BigInt(obj.sid as bigint | number),
@@ -169,18 +163,18 @@ function cborToEvent(obj: Record<string, unknown>): TraceEvent {
169
163
  streamType: obj.st as 0 | 1 | 2,
170
164
  };
171
165
 
172
- case 'stream-closed':
166
+ case "stream-closed":
173
167
  return {
174
- type: 'stream-closed' as const,
168
+ type: "stream-closed" as const,
175
169
  seq,
176
170
  timestamp,
177
171
  streamId: BigInt(obj.sid as bigint | number),
178
172
  errorCode: Number(obj.ec ?? 0),
179
173
  };
180
174
 
181
- case 'object-header':
175
+ case "object-header":
182
176
  return {
183
- type: 'object-header' as const,
177
+ type: "object-header" as const,
184
178
  seq,
185
179
  timestamp,
186
180
  streamId: BigInt(obj.sid as bigint | number),
@@ -190,9 +184,9 @@ function cborToEvent(obj: Record<string, unknown>): TraceEvent {
190
184
  objectStatus: Number(obj.os ?? 0),
191
185
  };
192
186
 
193
- case 'object-payload':
187
+ case "object-payload":
194
188
  return {
195
- type: 'object-payload' as const,
189
+ type: "object-payload" as const,
196
190
  seq,
197
191
  timestamp,
198
192
  streamId: BigInt(obj.sid as bigint | number),
@@ -202,30 +196,30 @@ function cborToEvent(obj: Record<string, unknown>): TraceEvent {
202
196
  ...(obj.pl != null ? { payload: obj.pl as Uint8Array } : {}),
203
197
  };
204
198
 
205
- case 'state-change':
199
+ case "state-change":
206
200
  return {
207
- type: 'state-change' as const,
201
+ type: "state-change" as const,
208
202
  seq,
209
203
  timestamp,
210
204
  from: obj.from as string,
211
205
  to: obj.to as string,
212
206
  };
213
207
 
214
- case 'error':
208
+ case "error":
215
209
  return {
216
- type: 'error' as const,
210
+ type: "error" as const,
217
211
  seq,
218
212
  timestamp,
219
213
  errorCode: Number(obj.ec ?? 0),
220
- reason: (obj.reason ?? '') as string,
214
+ reason: (obj.reason ?? "") as string,
221
215
  };
222
216
 
223
- case 'annotation':
217
+ case "annotation":
224
218
  return {
225
- type: 'annotation' as const,
219
+ type: "annotation" as const,
226
220
  seq,
227
221
  timestamp,
228
- label: (obj.label ?? '') as string,
222
+ label: (obj.label ?? "") as string,
229
223
  data: obj.data,
230
224
  };
231
225
  }
@@ -245,12 +239,12 @@ function writePreamble(headerCbor: Uint8Array): Uint8Array {
245
239
 
246
240
  function validatePreamble(bytes: Uint8Array): { version: number; headerLength: number } {
247
241
  if (bytes.length < PREAMBLE_SIZE) {
248
- throw new Error('File too short: expected at least 16 bytes');
242
+ throw new Error("File too short: expected at least 16 bytes");
249
243
  }
250
244
 
251
245
  for (let i = 0; i < 8; i++) {
252
246
  if (bytes[i] !== MAGIC[i]) {
253
- throw new Error('Invalid magic bytes: not a .moqtrace file');
247
+ throw new Error("Invalid magic bytes: not a .moqtrace file");
254
248
  }
255
249
  }
256
250
 
@@ -262,7 +256,7 @@ function validatePreamble(bytes: Uint8Array): { version: number; headerLength: n
262
256
 
263
257
  const headerLength = view.getUint32(12, true);
264
258
  if (PREAMBLE_SIZE + headerLength > bytes.length) {
265
- throw new Error('File truncated: header extends beyond file');
259
+ throw new Error("File truncated: header extends beyond file");
266
260
  }
267
261
 
268
262
  return { version, headerLength };
package/src/index.ts CHANGED
@@ -1,33 +1,32 @@
1
1
  // Binary .moqtrace format
2
+
3
+ export type { MoqtraceWriter } from "./binary.js";
2
4
  export {
3
- writeMoqtrace,
5
+ createMoqtraceWriter,
4
6
  readMoqtrace,
5
7
  readMoqtraceHeader,
6
- createMoqtraceWriter,
7
- } from './binary.js';
8
- export type { MoqtraceWriter } from './binary.js';
9
-
10
- // Recorder
11
- export { createRecorder } from './recorder.js';
12
- export type { TraceRecorder } from './recorder.js';
13
-
8
+ writeMoqtrace,
9
+ } from "./binary.js";
14
10
  // JSON (convenience)
15
- export { traceToJSON } from './json.js';
11
+ export { traceToJSON } from "./json.js";
12
+ export type { TraceRecorder } from "./recorder.js";
13
+ // Recorder
14
+ export { createRecorder } from "./recorder.js";
16
15
 
17
16
  // Types
18
17
  export type {
19
- Trace,
20
- TraceHeader,
21
- TraceEvent,
18
+ AnnotationEvent,
22
19
  ControlMessageEvent,
23
- StreamOpenedEvent,
24
- StreamClosedEvent,
20
+ DetailLevel,
25
21
  ObjectHeaderEvent,
26
22
  ObjectPayloadEvent,
27
- StateChangeEvent,
28
- TraceErrorEvent,
29
- AnnotationEvent,
30
- DetailLevel,
31
23
  Perspective,
32
24
  RecorderOptions,
33
- } from './types.js';
25
+ StateChangeEvent,
26
+ StreamClosedEvent,
27
+ StreamOpenedEvent,
28
+ Trace,
29
+ TraceErrorEvent,
30
+ TraceEvent,
31
+ TraceHeader,
32
+ } from "./types.js";
package/src/json.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Trace } from './types.js';
1
+ import type { Trace } from "./types.js";
2
2
 
3
3
  /**
4
4
  * Serialize a trace to human-readable JSON.
@@ -9,13 +9,19 @@ import type { Trace } from './types.js';
9
9
  * (`writeMoqtrace` / `readMoqtrace`).
10
10
  */
11
11
  export function traceToJSON(trace: Trace): string {
12
- return JSON.stringify(trace, (_key, value) => {
13
- if (typeof value === 'bigint') {
14
- return `0x${value.toString(16)}`;
15
- }
16
- if (value instanceof Uint8Array) {
17
- return Array.from(value).map(b => b.toString(16).padStart(2, '0')).join('');
18
- }
19
- return value;
20
- }, 2);
12
+ return JSON.stringify(
13
+ trace,
14
+ (_key, value) => {
15
+ if (typeof value === "bigint") {
16
+ return `0x${value.toString(16)}`;
17
+ }
18
+ if (value instanceof Uint8Array) {
19
+ return Array.from(value)
20
+ .map((b) => b.toString(16).padStart(2, "0"))
21
+ .join("");
22
+ }
23
+ return value;
24
+ },
25
+ 2,
26
+ );
21
27
  }
package/src/recorder.ts CHANGED
@@ -1,12 +1,6 @@
1
- import type { MoqtMessage } from '@moqtap/codec';
2
- import type { SessionState } from '@moqtap/codec/session';
3
- import type {
4
- Trace,
5
- TraceHeader,
6
- TraceEvent,
7
- RecorderOptions,
8
- DetailLevel,
9
- } from './types.js';
1
+ import type { MoqtMessage } from "@moqtap/codec";
2
+ import type { SessionState } from "@moqtap/codec/session";
3
+ import type { DetailLevel, RecorderOptions, Trace, TraceEvent, TraceHeader } from "./types.js";
10
4
 
11
5
  export interface TraceRecorder {
12
6
  /** Wrap a SessionState to auto-record control messages and state changes. */
@@ -23,14 +17,20 @@ export interface TraceRecorder {
23
17
 
24
18
  /** Record an object header event. Ignored below 'headers' detail level. */
25
19
  recordObjectHeader(
26
- streamId: bigint, groupId: bigint, objectId: bigint,
27
- publisherPriority: number, objectStatus: number,
20
+ streamId: bigint,
21
+ groupId: bigint,
22
+ objectId: bigint,
23
+ publisherPriority: number,
24
+ objectStatus: number,
28
25
  ): void;
29
26
 
30
27
  /** Record an object payload event. Ignored below 'headers+sizes' detail level. */
31
28
  recordObjectPayload(
32
- streamId: bigint, groupId: bigint, objectId: bigint,
33
- size: number, payload?: Uint8Array,
29
+ streamId: bigint,
30
+ groupId: bigint,
31
+ objectId: bigint,
32
+ size: number,
33
+ payload?: Uint8Array,
34
34
  ): void;
35
35
 
36
36
  /** Record a protocol error. */
@@ -47,11 +47,11 @@ export interface TraceRecorder {
47
47
  }
48
48
 
49
49
  const DETAIL_RANK: Record<DetailLevel, number> = {
50
- 'control': 0,
51
- 'headers': 1,
52
- 'headers+sizes': 2,
53
- 'headers+data': 3,
54
- 'full': 4,
50
+ control: 0,
51
+ headers: 1,
52
+ "headers+sizes": 2,
53
+ "headers+data": 3,
54
+ full: 4,
55
55
  };
56
56
 
57
57
  export function createRecorder(options: RecorderOptions): TraceRecorder {
@@ -80,19 +80,31 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
80
80
 
81
81
  function wrapSession(session: SessionState): SessionState {
82
82
  return {
83
- get phase() { return session.phase; },
84
- get role() { return session.role; },
85
- get subscriptions() { return session.subscriptions; },
86
- get announces() { return session.announces; },
87
- get legalOutgoing() { return session.legalOutgoing; },
88
- get legalIncoming() { return session.legalIncoming; },
83
+ get phase() {
84
+ return session.phase;
85
+ },
86
+ get role() {
87
+ return session.role;
88
+ },
89
+ get subscriptions() {
90
+ return session.subscriptions;
91
+ },
92
+ get announces() {
93
+ return session.announces;
94
+ },
95
+ get legalOutgoing() {
96
+ return session.legalOutgoing;
97
+ },
98
+ get legalIncoming() {
99
+ return session.legalIncoming;
100
+ },
89
101
 
90
102
  receive(message: MoqtMessage) {
91
103
  const prevPhase = session.phase;
92
104
  const result = session.receive(message);
93
105
 
94
106
  addEvent({
95
- type: 'control',
107
+ type: "control",
96
108
  seq: nextSeq(),
97
109
  timestamp: clock(),
98
110
  direction: 1, // rx
@@ -102,7 +114,7 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
102
114
 
103
115
  if (result.ok && result.phase !== prevPhase) {
104
116
  addEvent({
105
- type: 'state-change',
117
+ type: "state-change",
106
118
  seq: nextSeq(),
107
119
  timestamp: clock(),
108
120
  from: prevPhase,
@@ -122,7 +134,7 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
122
134
  const result = session.send(message);
123
135
 
124
136
  addEvent({
125
- type: 'control',
137
+ type: "control",
126
138
  seq: nextSeq(),
127
139
  timestamp: clock(),
128
140
  direction: 0, // tx
@@ -132,7 +144,7 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
132
144
 
133
145
  if (result.ok && result.phase !== prevPhase) {
134
146
  addEvent({
135
- type: 'state-change',
147
+ type: "state-change",
136
148
  seq: nextSeq(),
137
149
  timestamp: clock(),
138
150
  from: prevPhase,
@@ -155,9 +167,9 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
155
167
  record: addEvent,
156
168
 
157
169
  recordStreamOpened(streamId, direction, streamType) {
158
- if (detailRank < DETAIL_RANK['headers']) return;
170
+ if (detailRank < DETAIL_RANK.headers) return;
159
171
  addEvent({
160
- type: 'stream-opened',
172
+ type: "stream-opened",
161
173
  seq: nextSeq(),
162
174
  timestamp: clock(),
163
175
  streamId,
@@ -167,9 +179,9 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
167
179
  },
168
180
 
169
181
  recordStreamClosed(streamId, errorCode = 0) {
170
- if (detailRank < DETAIL_RANK['headers']) return;
182
+ if (detailRank < DETAIL_RANK.headers) return;
171
183
  addEvent({
172
- type: 'stream-closed',
184
+ type: "stream-closed",
173
185
  seq: nextSeq(),
174
186
  timestamp: clock(),
175
187
  streamId,
@@ -178,9 +190,9 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
178
190
  },
179
191
 
180
192
  recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus) {
181
- if (detailRank < DETAIL_RANK['headers']) return;
193
+ if (detailRank < DETAIL_RANK.headers) return;
182
194
  addEvent({
183
- type: 'object-header',
195
+ type: "object-header",
184
196
  seq: nextSeq(),
185
197
  timestamp: clock(),
186
198
  streamId,
@@ -192,23 +204,23 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
192
204
  },
193
205
 
194
206
  recordObjectPayload(streamId, groupId, objectId, size, payload) {
195
- if (detailRank < DETAIL_RANK['headers+sizes']) return;
207
+ if (detailRank < DETAIL_RANK["headers+sizes"]) return;
196
208
  const event: TraceEvent = {
197
- type: 'object-payload',
209
+ type: "object-payload",
198
210
  seq: nextSeq(),
199
211
  timestamp: clock(),
200
212
  streamId,
201
213
  groupId,
202
214
  objectId,
203
215
  size,
204
- ...(detailRank >= DETAIL_RANK['headers+data'] && payload != null ? { payload } : {}),
216
+ ...(detailRank >= DETAIL_RANK["headers+data"] && payload != null ? { payload } : {}),
205
217
  };
206
218
  addEvent(event);
207
219
  },
208
220
 
209
221
  recordError(errorCode, reason) {
210
222
  addEvent({
211
- type: 'error',
223
+ type: "error",
212
224
  seq: nextSeq(),
213
225
  timestamp: clock(),
214
226
  errorCode,
@@ -218,7 +230,7 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
218
230
 
219
231
  annotate(label, data) {
220
232
  addEvent({
221
- type: 'annotation',
233
+ type: "annotation",
222
234
  seq: nextSeq(),
223
235
  timestamp: clock(),
224
236
  label,
@@ -242,6 +254,8 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
242
254
  return { header, events: [...events] };
243
255
  },
244
256
 
245
- get recording() { return _recording; },
257
+ get recording() {
258
+ return _recording;
259
+ },
246
260
  };
247
261
  }
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type DetailLevel = 'control' | 'headers' | 'headers+sizes' | 'headers+data' | 'full';
2
- export type Perspective = 'client' | 'server' | 'observer';
1
+ export type DetailLevel = "control" | "headers" | "headers+sizes" | "headers+data" | "full";
2
+ export type Perspective = "client" | "server" | "observer";
3
3
 
4
4
  export interface TraceHeader {
5
5
  readonly protocol: string;
@@ -20,7 +20,7 @@ interface BaseEvent {
20
20
  }
21
21
 
22
22
  export interface ControlMessageEvent extends BaseEvent {
23
- readonly type: 'control';
23
+ readonly type: "control";
24
24
  readonly direction: 0 | 1;
25
25
  readonly messageType: number;
26
26
  readonly message: Record<string, unknown>;
@@ -28,20 +28,20 @@ export interface ControlMessageEvent extends BaseEvent {
28
28
  }
29
29
 
30
30
  export interface StreamOpenedEvent extends BaseEvent {
31
- readonly type: 'stream-opened';
31
+ readonly type: "stream-opened";
32
32
  readonly streamId: bigint;
33
33
  readonly direction: 0 | 1;
34
34
  readonly streamType: 0 | 1 | 2;
35
35
  }
36
36
 
37
37
  export interface StreamClosedEvent extends BaseEvent {
38
- readonly type: 'stream-closed';
38
+ readonly type: "stream-closed";
39
39
  readonly streamId: bigint;
40
40
  readonly errorCode: number;
41
41
  }
42
42
 
43
43
  export interface ObjectHeaderEvent extends BaseEvent {
44
- readonly type: 'object-header';
44
+ readonly type: "object-header";
45
45
  readonly streamId: bigint;
46
46
  readonly groupId: bigint;
47
47
  readonly objectId: bigint;
@@ -50,7 +50,7 @@ export interface ObjectHeaderEvent extends BaseEvent {
50
50
  }
51
51
 
52
52
  export interface ObjectPayloadEvent extends BaseEvent {
53
- readonly type: 'object-payload';
53
+ readonly type: "object-payload";
54
54
  readonly streamId: bigint;
55
55
  readonly groupId: bigint;
56
56
  readonly objectId: bigint;
@@ -59,19 +59,19 @@ export interface ObjectPayloadEvent extends BaseEvent {
59
59
  }
60
60
 
61
61
  export interface StateChangeEvent extends BaseEvent {
62
- readonly type: 'state-change';
62
+ readonly type: "state-change";
63
63
  readonly from: string;
64
64
  readonly to: string;
65
65
  }
66
66
 
67
67
  export interface TraceErrorEvent extends BaseEvent {
68
- readonly type: 'error';
68
+ readonly type: "error";
69
69
  readonly errorCode: number;
70
70
  readonly reason: string;
71
71
  }
72
72
 
73
73
  export interface AnnotationEvent extends BaseEvent {
74
- readonly type: 'annotation';
74
+ readonly type: "annotation";
75
75
  readonly label: string;
76
76
  readonly data: unknown;
77
77
  }