@moqtap/trace 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,493 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/binary.ts
2
+ var _cborx = require('cbor-x');
3
+ var MAGIC = new Uint8Array([77, 79, 81, 84, 82, 65, 67, 69]);
4
+ var FORMAT_VERSION = 1;
5
+ var PREAMBLE_SIZE = 16;
6
+ var EVENT_TYPE_TO_INT = {
7
+ "control": 0,
8
+ "stream-opened": 1,
9
+ "stream-closed": 2,
10
+ "object-header": 3,
11
+ "object-payload": 4,
12
+ "state-change": 5,
13
+ "error": 6,
14
+ "annotation": 7
15
+ };
16
+ var INT_TO_EVENT_TYPE = {
17
+ 0: "control",
18
+ 1: "stream-opened",
19
+ 2: "stream-closed",
20
+ 3: "object-header",
21
+ 4: "object-payload",
22
+ 5: "state-change",
23
+ 6: "error",
24
+ 7: "annotation"
25
+ };
26
+ function headerToCbor(header) {
27
+ const map = {
28
+ protocol: header.protocol,
29
+ perspective: header.perspective,
30
+ detail: header.detail,
31
+ startTime: header.startTime
32
+ };
33
+ if (header.endTime != null) map.endTime = header.endTime;
34
+ if (header.transport != null) map.transport = header.transport;
35
+ if (header.source != null) map.source = header.source;
36
+ if (header.endpoint != null) map.endpoint = header.endpoint;
37
+ if (header.sessionId != null) map.sessionId = header.sessionId;
38
+ if (header.custom != null) map.custom = header.custom;
39
+ return map;
40
+ }
41
+ function cborToHeader(obj) {
42
+ return {
43
+ protocol: obj.protocol,
44
+ perspective: obj.perspective,
45
+ detail: obj.detail,
46
+ startTime: obj.startTime,
47
+ ...obj.endTime != null ? { endTime: obj.endTime } : {},
48
+ ...obj.transport != null ? { transport: obj.transport } : {},
49
+ ...obj.source != null ? { source: obj.source } : {},
50
+ ...obj.endpoint != null ? { endpoint: obj.endpoint } : {},
51
+ ...obj.sessionId != null ? { sessionId: obj.sessionId } : {},
52
+ ...obj.custom != null ? { custom: obj.custom } : {}
53
+ };
54
+ }
55
+ function eventToCbor(event) {
56
+ const base = {
57
+ n: event.seq,
58
+ t: event.timestamp,
59
+ e: EVENT_TYPE_TO_INT[event.type]
60
+ };
61
+ switch (event.type) {
62
+ case "control": {
63
+ base.d = event.direction;
64
+ base.mt = event.messageType;
65
+ base.msg = event.message;
66
+ if (event.raw != null) base.raw = event.raw;
67
+ break;
68
+ }
69
+ case "stream-opened": {
70
+ base.sid = event.streamId;
71
+ base.d = event.direction;
72
+ base.st = event.streamType;
73
+ break;
74
+ }
75
+ case "stream-closed": {
76
+ base.sid = event.streamId;
77
+ base.ec = event.errorCode;
78
+ break;
79
+ }
80
+ case "object-header": {
81
+ base.sid = event.streamId;
82
+ base.g = event.groupId;
83
+ base.o = event.objectId;
84
+ base.pp = event.publisherPriority;
85
+ base.os = event.objectStatus;
86
+ break;
87
+ }
88
+ case "object-payload": {
89
+ base.sid = event.streamId;
90
+ base.g = event.groupId;
91
+ base.o = event.objectId;
92
+ base.sz = event.size;
93
+ if (event.payload != null) base.pl = event.payload;
94
+ break;
95
+ }
96
+ case "state-change": {
97
+ base.from = event.from;
98
+ base.to = event.to;
99
+ break;
100
+ }
101
+ case "error": {
102
+ base.ec = event.errorCode;
103
+ base.reason = event.reason;
104
+ break;
105
+ }
106
+ case "annotation": {
107
+ base.label = event.label;
108
+ if (event.data !== void 0) base.data = event.data;
109
+ break;
110
+ }
111
+ }
112
+ return base;
113
+ }
114
+ function cborToEvent(obj) {
115
+ const eventType = INT_TO_EVENT_TYPE[obj.e];
116
+ if (eventType == null) {
117
+ return {
118
+ type: "annotation",
119
+ seq: Number(_nullishCoalesce(obj.n, () => ( 0))),
120
+ timestamp: Number(_nullishCoalesce(obj.t, () => ( 0))),
121
+ label: `unknown-event-${obj.e}`,
122
+ data: obj
123
+ };
124
+ }
125
+ const seq = Number(_nullishCoalesce(obj.n, () => ( 0)));
126
+ const timestamp = Number(_nullishCoalesce(obj.t, () => ( 0)));
127
+ switch (eventType) {
128
+ case "control":
129
+ return {
130
+ type: "control",
131
+ seq,
132
+ timestamp,
133
+ direction: obj.d,
134
+ messageType: Number(_nullishCoalesce(obj.mt, () => ( 0))),
135
+ message: _nullishCoalesce(obj.msg, () => ( {})),
136
+ ...obj.raw != null ? { raw: obj.raw } : {}
137
+ };
138
+ case "stream-opened":
139
+ return {
140
+ type: "stream-opened",
141
+ seq,
142
+ timestamp,
143
+ streamId: BigInt(obj.sid),
144
+ direction: obj.d,
145
+ streamType: obj.st
146
+ };
147
+ case "stream-closed":
148
+ return {
149
+ type: "stream-closed",
150
+ seq,
151
+ timestamp,
152
+ streamId: BigInt(obj.sid),
153
+ errorCode: Number(_nullishCoalesce(obj.ec, () => ( 0)))
154
+ };
155
+ case "object-header":
156
+ return {
157
+ type: "object-header",
158
+ seq,
159
+ timestamp,
160
+ streamId: BigInt(obj.sid),
161
+ groupId: BigInt(obj.g),
162
+ objectId: BigInt(obj.o),
163
+ publisherPriority: Number(_nullishCoalesce(obj.pp, () => ( 0))),
164
+ objectStatus: Number(_nullishCoalesce(obj.os, () => ( 0)))
165
+ };
166
+ case "object-payload":
167
+ return {
168
+ type: "object-payload",
169
+ seq,
170
+ timestamp,
171
+ streamId: BigInt(obj.sid),
172
+ groupId: BigInt(obj.g),
173
+ objectId: BigInt(obj.o),
174
+ size: Number(_nullishCoalesce(obj.sz, () => ( 0))),
175
+ ...obj.pl != null ? { payload: obj.pl } : {}
176
+ };
177
+ case "state-change":
178
+ return {
179
+ type: "state-change",
180
+ seq,
181
+ timestamp,
182
+ from: obj.from,
183
+ to: obj.to
184
+ };
185
+ case "error":
186
+ return {
187
+ type: "error",
188
+ seq,
189
+ timestamp,
190
+ errorCode: Number(_nullishCoalesce(obj.ec, () => ( 0))),
191
+ reason: _nullishCoalesce(obj.reason, () => ( ""))
192
+ };
193
+ case "annotation":
194
+ return {
195
+ type: "annotation",
196
+ seq,
197
+ timestamp,
198
+ label: _nullishCoalesce(obj.label, () => ( "")),
199
+ data: obj.data
200
+ };
201
+ }
202
+ }
203
+ function writePreamble(headerCbor) {
204
+ const buf = new Uint8Array(PREAMBLE_SIZE + headerCbor.length);
205
+ buf.set(MAGIC, 0);
206
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
207
+ view.setUint32(8, FORMAT_VERSION, true);
208
+ view.setUint32(12, headerCbor.length, true);
209
+ buf.set(headerCbor, PREAMBLE_SIZE);
210
+ return buf;
211
+ }
212
+ function validatePreamble(bytes) {
213
+ if (bytes.length < PREAMBLE_SIZE) {
214
+ throw new Error("File too short: expected at least 16 bytes");
215
+ }
216
+ for (let i = 0; i < 8; i++) {
217
+ if (bytes[i] !== MAGIC[i]) {
218
+ throw new Error("Invalid magic bytes: not a .moqtrace file");
219
+ }
220
+ }
221
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
222
+ const version = view.getUint32(8, true);
223
+ if (version !== FORMAT_VERSION) {
224
+ throw new Error(`Unsupported format version: ${version} (expected ${FORMAT_VERSION})`);
225
+ }
226
+ const headerLength = view.getUint32(12, true);
227
+ if (PREAMBLE_SIZE + headerLength > bytes.length) {
228
+ throw new Error("File truncated: header extends beyond file");
229
+ }
230
+ return { version, headerLength };
231
+ }
232
+ function writeMoqtrace(trace) {
233
+ const headerCbor = _cborx.encode.call(void 0, headerToCbor(trace.header));
234
+ const preamble = writePreamble(headerCbor);
235
+ const eventChunks = [];
236
+ let totalEventBytes = 0;
237
+ for (const event of trace.events) {
238
+ const chunk = _cborx.encode.call(void 0, eventToCbor(event));
239
+ eventChunks.push(chunk);
240
+ totalEventBytes += chunk.length;
241
+ }
242
+ const result = new Uint8Array(preamble.length + totalEventBytes);
243
+ result.set(preamble, 0);
244
+ let offset = preamble.length;
245
+ for (const chunk of eventChunks) {
246
+ result.set(chunk, offset);
247
+ offset += chunk.length;
248
+ }
249
+ return result;
250
+ }
251
+ function readMoqtrace(bytes) {
252
+ const { headerLength } = validatePreamble(bytes);
253
+ const headerBytes = bytes.slice(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength);
254
+ const header = cborToHeader(_cborx.decode.call(void 0, headerBytes));
255
+ const eventBytes = bytes.slice(PREAMBLE_SIZE + headerLength);
256
+ const events = [];
257
+ if (eventBytes.length > 0) {
258
+ _cborx.decodeMultiple.call(void 0, eventBytes, (obj) => {
259
+ events.push(cborToEvent(obj));
260
+ });
261
+ }
262
+ return { header, events };
263
+ }
264
+ function readMoqtraceHeader(bytes) {
265
+ const { headerLength } = validatePreamble(bytes);
266
+ const headerBytes = bytes.slice(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength);
267
+ return cborToHeader(_cborx.decode.call(void 0, headerBytes));
268
+ }
269
+ function createMoqtraceWriter(header) {
270
+ const headerCbor = _cborx.encode.call(void 0, headerToCbor(header));
271
+ const preambleBytes = writePreamble(headerCbor);
272
+ return {
273
+ preamble() {
274
+ return preambleBytes;
275
+ },
276
+ writeEvent(event) {
277
+ return _cborx.encode.call(void 0, eventToCbor(event));
278
+ }
279
+ };
280
+ }
281
+
282
+ // src/recorder.ts
283
+ var DETAIL_RANK = {
284
+ "control": 0,
285
+ "headers": 1,
286
+ "headers+sizes": 2,
287
+ "headers+data": 3,
288
+ "full": 4
289
+ };
290
+ function createRecorder(options) {
291
+ const detail = options.detail;
292
+ const detailRank = DETAIL_RANK[detail];
293
+ const maxEvents = _nullishCoalesce(options.maxEvents, () => ( 1e5));
294
+ const clock = _nullishCoalesce(options.clock, () => ( (() => Math.round(performance.now() * 1e3))));
295
+ const messageTypeId = _nullishCoalesce(options.messageTypeId, () => ( (() => 0)));
296
+ const events = [];
297
+ let _recording = true;
298
+ let _seq = 0;
299
+ const startTime = Date.now();
300
+ function addEvent(event) {
301
+ if (!_recording) return;
302
+ if (events.length >= maxEvents) {
303
+ events.shift();
304
+ }
305
+ events.push(event);
306
+ }
307
+ function nextSeq() {
308
+ return _seq++;
309
+ }
310
+ function wrapSession(session) {
311
+ return {
312
+ get phase() {
313
+ return session.phase;
314
+ },
315
+ get role() {
316
+ return session.role;
317
+ },
318
+ get subscriptions() {
319
+ return session.subscriptions;
320
+ },
321
+ get announces() {
322
+ return session.announces;
323
+ },
324
+ get legalOutgoing() {
325
+ return session.legalOutgoing;
326
+ },
327
+ get legalIncoming() {
328
+ return session.legalIncoming;
329
+ },
330
+ receive(message) {
331
+ const prevPhase = session.phase;
332
+ const result = session.receive(message);
333
+ addEvent({
334
+ type: "control",
335
+ seq: nextSeq(),
336
+ timestamp: clock(),
337
+ direction: 1,
338
+ // rx
339
+ messageType: messageTypeId(message.type),
340
+ message
341
+ });
342
+ if (result.ok && result.phase !== prevPhase) {
343
+ addEvent({
344
+ type: "state-change",
345
+ seq: nextSeq(),
346
+ timestamp: clock(),
347
+ from: prevPhase,
348
+ to: result.phase
349
+ });
350
+ }
351
+ return result;
352
+ },
353
+ validateOutgoing(message) {
354
+ return session.validateOutgoing(message);
355
+ },
356
+ send(message) {
357
+ const prevPhase = session.phase;
358
+ const result = session.send(message);
359
+ addEvent({
360
+ type: "control",
361
+ seq: nextSeq(),
362
+ timestamp: clock(),
363
+ direction: 0,
364
+ // tx
365
+ messageType: messageTypeId(message.type),
366
+ message
367
+ });
368
+ if (result.ok && result.phase !== prevPhase) {
369
+ addEvent({
370
+ type: "state-change",
371
+ seq: nextSeq(),
372
+ timestamp: clock(),
373
+ from: prevPhase,
374
+ to: result.phase
375
+ });
376
+ }
377
+ return result;
378
+ },
379
+ reset() {
380
+ session.reset();
381
+ }
382
+ };
383
+ }
384
+ return {
385
+ wrapSession,
386
+ record: addEvent,
387
+ recordStreamOpened(streamId, direction, streamType) {
388
+ if (detailRank < DETAIL_RANK["headers"]) return;
389
+ addEvent({
390
+ type: "stream-opened",
391
+ seq: nextSeq(),
392
+ timestamp: clock(),
393
+ streamId,
394
+ direction,
395
+ streamType
396
+ });
397
+ },
398
+ recordStreamClosed(streamId, errorCode = 0) {
399
+ if (detailRank < DETAIL_RANK["headers"]) return;
400
+ addEvent({
401
+ type: "stream-closed",
402
+ seq: nextSeq(),
403
+ timestamp: clock(),
404
+ streamId,
405
+ errorCode
406
+ });
407
+ },
408
+ recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus) {
409
+ if (detailRank < DETAIL_RANK["headers"]) return;
410
+ addEvent({
411
+ type: "object-header",
412
+ seq: nextSeq(),
413
+ timestamp: clock(),
414
+ streamId,
415
+ groupId,
416
+ objectId,
417
+ publisherPriority,
418
+ objectStatus
419
+ });
420
+ },
421
+ recordObjectPayload(streamId, groupId, objectId, size, payload) {
422
+ if (detailRank < DETAIL_RANK["headers+sizes"]) return;
423
+ const event = {
424
+ type: "object-payload",
425
+ seq: nextSeq(),
426
+ timestamp: clock(),
427
+ streamId,
428
+ groupId,
429
+ objectId,
430
+ size,
431
+ ...detailRank >= DETAIL_RANK["headers+data"] && payload != null ? { payload } : {}
432
+ };
433
+ addEvent(event);
434
+ },
435
+ recordError(errorCode, reason) {
436
+ addEvent({
437
+ type: "error",
438
+ seq: nextSeq(),
439
+ timestamp: clock(),
440
+ errorCode,
441
+ reason
442
+ });
443
+ },
444
+ annotate(label, data) {
445
+ addEvent({
446
+ type: "annotation",
447
+ seq: nextSeq(),
448
+ timestamp: clock(),
449
+ label,
450
+ data
451
+ });
452
+ },
453
+ finalize() {
454
+ _recording = false;
455
+ const header = {
456
+ protocol: options.protocol,
457
+ perspective: options.perspective,
458
+ detail,
459
+ startTime,
460
+ endTime: Date.now(),
461
+ ...options.transport != null ? { transport: options.transport } : {},
462
+ ...options.source != null ? { source: options.source } : {},
463
+ ...options.endpoint != null ? { endpoint: options.endpoint } : {},
464
+ ...options.sessionId != null ? { sessionId: options.sessionId } : {}
465
+ };
466
+ return { header, events: [...events] };
467
+ },
468
+ get recording() {
469
+ return _recording;
470
+ }
471
+ };
472
+ }
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
+
487
+
488
+
489
+
490
+
491
+
492
+
493
+ exports.createMoqtraceWriter = createMoqtraceWriter; exports.createRecorder = createRecorder; exports.readMoqtrace = readMoqtrace; exports.readMoqtraceHeader = readMoqtraceHeader; exports.traceToJSON = traceToJSON; exports.writeMoqtrace = writeMoqtrace;
@@ -0,0 +1,146 @@
1
+ import { SessionState } from '@moqtap/codec/session';
2
+
3
+ type DetailLevel = 'control' | 'headers' | 'headers+sizes' | 'headers+data' | 'full';
4
+ type Perspective = 'client' | 'server' | 'observer';
5
+ interface TraceHeader {
6
+ readonly protocol: string;
7
+ readonly perspective: Perspective;
8
+ readonly detail: DetailLevel;
9
+ readonly startTime: number;
10
+ readonly endTime?: number;
11
+ readonly transport?: string;
12
+ readonly source?: string;
13
+ readonly endpoint?: string;
14
+ readonly sessionId?: string;
15
+ readonly custom?: Record<string, unknown>;
16
+ }
17
+ interface BaseEvent {
18
+ readonly seq: number;
19
+ readonly timestamp: number;
20
+ }
21
+ interface ControlMessageEvent extends BaseEvent {
22
+ readonly type: 'control';
23
+ readonly direction: 0 | 1;
24
+ readonly messageType: number;
25
+ readonly message: Record<string, unknown>;
26
+ readonly raw?: Uint8Array;
27
+ }
28
+ interface StreamOpenedEvent extends BaseEvent {
29
+ readonly type: 'stream-opened';
30
+ readonly streamId: bigint;
31
+ readonly direction: 0 | 1;
32
+ readonly streamType: 0 | 1 | 2;
33
+ }
34
+ interface StreamClosedEvent extends BaseEvent {
35
+ readonly type: 'stream-closed';
36
+ readonly streamId: bigint;
37
+ readonly errorCode: number;
38
+ }
39
+ interface ObjectHeaderEvent extends BaseEvent {
40
+ readonly type: 'object-header';
41
+ readonly streamId: bigint;
42
+ readonly groupId: bigint;
43
+ readonly objectId: bigint;
44
+ readonly publisherPriority: number;
45
+ readonly objectStatus: number;
46
+ }
47
+ interface ObjectPayloadEvent extends BaseEvent {
48
+ readonly type: 'object-payload';
49
+ readonly streamId: bigint;
50
+ readonly groupId: bigint;
51
+ readonly objectId: bigint;
52
+ readonly size: number;
53
+ readonly payload?: Uint8Array;
54
+ }
55
+ interface StateChangeEvent extends BaseEvent {
56
+ readonly type: 'state-change';
57
+ readonly from: string;
58
+ readonly to: string;
59
+ }
60
+ interface TraceErrorEvent extends BaseEvent {
61
+ readonly type: 'error';
62
+ readonly errorCode: number;
63
+ readonly reason: string;
64
+ }
65
+ interface AnnotationEvent extends BaseEvent {
66
+ readonly type: 'annotation';
67
+ readonly label: string;
68
+ readonly data: unknown;
69
+ }
70
+ type TraceEvent = ControlMessageEvent | StreamOpenedEvent | StreamClosedEvent | ObjectHeaderEvent | ObjectPayloadEvent | StateChangeEvent | TraceErrorEvent | AnnotationEvent;
71
+ interface Trace {
72
+ readonly header: TraceHeader;
73
+ readonly events: TraceEvent[];
74
+ }
75
+ interface RecorderOptions {
76
+ readonly detail: DetailLevel;
77
+ readonly protocol: string;
78
+ readonly perspective: Perspective;
79
+ readonly transport?: string;
80
+ readonly source?: string;
81
+ readonly endpoint?: string;
82
+ readonly sessionId?: string;
83
+ readonly maxEvents?: number;
84
+ readonly clock?: () => number;
85
+ /** Map message type name (e.g. 'subscribe') to wire ID (e.g. 0x03). Required for session-layer recording. */
86
+ readonly messageTypeId?: (name: string) => number;
87
+ }
88
+
89
+ /**
90
+ * Write a complete trace to .moqtrace binary format.
91
+ */
92
+ declare function writeMoqtrace(trace: Trace): Uint8Array;
93
+ /**
94
+ * Read a complete .moqtrace file from bytes.
95
+ */
96
+ declare function readMoqtrace(bytes: Uint8Array): Trace;
97
+ /**
98
+ * Read only the header from a .moqtrace file (fast metadata peek).
99
+ */
100
+ declare function readMoqtraceHeader(bytes: Uint8Array): TraceHeader;
101
+ /**
102
+ * Streaming writer for building .moqtrace files incrementally.
103
+ */
104
+ interface MoqtraceWriter {
105
+ /** Returns the file preamble (magic + version + header). Write this first. */
106
+ preamble(): Uint8Array;
107
+ /** Encode a single event. Append the returned bytes after the preamble. */
108
+ writeEvent(event: TraceEvent): Uint8Array;
109
+ }
110
+ declare function createMoqtraceWriter(header: TraceHeader): MoqtraceWriter;
111
+
112
+ interface TraceRecorder {
113
+ /** Wrap a SessionState to auto-record control messages and state changes. */
114
+ wrapSession(session: SessionState): SessionState;
115
+ /** Record an arbitrary event manually. */
116
+ record(event: TraceEvent): void;
117
+ /** Record a stream-opened event. Ignored at 'control' detail level. */
118
+ recordStreamOpened(streamId: bigint, direction: 0 | 1, streamType: 0 | 1 | 2): void;
119
+ /** Record a stream-closed event. Ignored at 'control' detail level. */
120
+ recordStreamClosed(streamId: bigint, errorCode?: number): void;
121
+ /** Record an object header event. Ignored below 'headers' detail level. */
122
+ recordObjectHeader(streamId: bigint, groupId: bigint, objectId: bigint, publisherPriority: number, objectStatus: number): void;
123
+ /** Record an object payload event. Ignored below 'headers+sizes' detail level. */
124
+ recordObjectPayload(streamId: bigint, groupId: bigint, objectId: bigint, size: number, payload?: Uint8Array): void;
125
+ /** Record a protocol error. */
126
+ recordError(errorCode: number, reason: string): void;
127
+ /** Record a user-defined annotation. */
128
+ annotate(label: string, data?: unknown): void;
129
+ /** Finalize the trace. Stops recording and returns the trace. */
130
+ finalize(): Trace;
131
+ /** Whether the recorder is still accepting events. */
132
+ readonly recording: boolean;
133
+ }
134
+ declare function createRecorder(options: RecorderOptions): TraceRecorder;
135
+
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
+ 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 };