@moqtap/trace 0.1.1 → 0.1.2

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/src/binary.ts CHANGED
@@ -1,32 +1,32 @@
1
- import { decode, decodeMultiple, encode } from "cbor-x";
2
- import type { DetailLevel, Perspective, Trace, TraceEvent, TraceHeader } from "./types.js";
1
+ import { decode, decodeMultiple, encode } from 'cbor-x'
2
+ import type { DetailLevel, Perspective, Trace, TraceEvent, TraceHeader } from './types.js'
3
3
 
4
- const MAGIC = new Uint8Array([0x4d, 0x4f, 0x51, 0x54, 0x52, 0x41, 0x43, 0x45]); // "MOQTRACE"
5
- const FORMAT_VERSION = 1;
6
- const PREAMBLE_SIZE = 16; // 8 magic + 4 version + 4 header length
4
+ const MAGIC = new Uint8Array([0x4d, 0x4f, 0x51, 0x54, 0x52, 0x41, 0x43, 0x45]) // "MOQTRACE"
5
+ const FORMAT_VERSION = 1
6
+ const PREAMBLE_SIZE = 16 // 8 magic + 4 version + 4 header length
7
7
 
8
8
  // Event type string ↔ integer mapping
9
- const EVENT_TYPE_TO_INT: Record<TraceEvent["type"], number> = {
9
+ const EVENT_TYPE_TO_INT: Record<TraceEvent['type'], number> = {
10
10
  control: 0,
11
- "stream-opened": 1,
12
- "stream-closed": 2,
13
- "object-header": 3,
14
- "object-payload": 4,
15
- "state-change": 5,
11
+ 'stream-opened': 1,
12
+ 'stream-closed': 2,
13
+ 'object-header': 3,
14
+ 'object-payload': 4,
15
+ 'state-change': 5,
16
16
  error: 6,
17
17
  annotation: 7,
18
- };
19
-
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",
29
- };
18
+ }
19
+
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',
29
+ }
30
30
 
31
31
  // --- Header serialization ---
32
32
 
@@ -36,14 +36,14 @@ function headerToCbor(header: TraceHeader): Record<string, unknown> {
36
36
  perspective: header.perspective,
37
37
  detail: header.detail,
38
38
  startTime: header.startTime,
39
- };
40
- if (header.endTime != null) map.endTime = header.endTime;
41
- if (header.transport != null) map.transport = header.transport;
42
- if (header.source != null) map.source = header.source;
43
- if (header.endpoint != null) map.endpoint = header.endpoint;
44
- if (header.sessionId != null) map.sessionId = header.sessionId;
45
- if (header.custom != null) map.custom = header.custom;
46
- return map;
39
+ }
40
+ if (header.endTime != null) map.endTime = header.endTime
41
+ if (header.transport != null) map.transport = header.transport
42
+ if (header.source != null) map.source = header.source
43
+ if (header.endpoint != null) map.endpoint = header.endpoint
44
+ if (header.sessionId != null) map.sessionId = header.sessionId
45
+ if (header.custom != null) map.custom = header.custom
46
+ return map
47
47
  }
48
48
 
49
49
  function cborToHeader(obj: Record<string, unknown>): TraceHeader {
@@ -58,7 +58,7 @@ function cborToHeader(obj: Record<string, unknown>): TraceHeader {
58
58
  ...(obj.endpoint != null ? { endpoint: obj.endpoint as string } : {}),
59
59
  ...(obj.sessionId != null ? { sessionId: obj.sessionId as string } : {}),
60
60
  ...(obj.custom != null ? { custom: obj.custom as Record<string, unknown> } : {}),
61
- };
61
+ }
62
62
  }
63
63
 
64
64
  // --- Event serialization ---
@@ -68,113 +68,113 @@ function eventToCbor(event: TraceEvent): Record<string, unknown> {
68
68
  n: event.seq,
69
69
  t: event.timestamp,
70
70
  e: EVENT_TYPE_TO_INT[event.type],
71
- };
71
+ }
72
72
 
73
73
  switch (event.type) {
74
- case "control": {
75
- base.d = event.direction;
76
- base.mt = event.messageType;
77
- base.msg = event.message;
78
- if (event.raw != null) base.raw = event.raw;
79
- break;
74
+ case 'control': {
75
+ base.d = event.direction
76
+ base.mt = event.messageType
77
+ base.msg = event.message
78
+ if (event.raw != null) base.raw = event.raw
79
+ break
80
80
  }
81
- case "stream-opened": {
82
- base.sid = event.streamId;
83
- base.d = event.direction;
84
- base.st = event.streamType;
85
- break;
81
+ case 'stream-opened': {
82
+ base.sid = event.streamId
83
+ base.d = event.direction
84
+ base.st = event.streamType
85
+ break
86
86
  }
87
- case "stream-closed": {
88
- base.sid = event.streamId;
89
- base.ec = event.errorCode;
90
- break;
87
+ case 'stream-closed': {
88
+ base.sid = event.streamId
89
+ base.ec = event.errorCode
90
+ break
91
91
  }
92
- case "object-header": {
93
- base.sid = event.streamId;
94
- base.g = event.groupId;
95
- base.o = event.objectId;
96
- base.pp = event.publisherPriority;
97
- base.os = event.objectStatus;
98
- break;
92
+ case 'object-header': {
93
+ base.sid = event.streamId
94
+ base.g = event.groupId
95
+ base.o = event.objectId
96
+ base.pp = event.publisherPriority
97
+ base.os = event.objectStatus
98
+ break
99
99
  }
100
- case "object-payload": {
101
- base.sid = event.streamId;
102
- base.g = event.groupId;
103
- base.o = event.objectId;
104
- base.sz = event.size;
105
- if (event.payload != null) base.pl = event.payload;
106
- break;
100
+ case 'object-payload': {
101
+ base.sid = event.streamId
102
+ base.g = event.groupId
103
+ base.o = event.objectId
104
+ base.sz = event.size
105
+ if (event.payload != null) base.pl = event.payload
106
+ break
107
107
  }
108
- case "state-change": {
109
- base.from = event.from;
110
- base.to = event.to;
111
- break;
108
+ case 'state-change': {
109
+ base.from = event.from
110
+ base.to = event.to
111
+ break
112
112
  }
113
- case "error": {
114
- base.ec = event.errorCode;
115
- base.reason = event.reason;
116
- break;
113
+ case 'error': {
114
+ base.ec = event.errorCode
115
+ base.reason = event.reason
116
+ break
117
117
  }
118
- case "annotation": {
119
- base.label = event.label;
120
- if (event.data !== undefined) base.data = event.data;
121
- break;
118
+ case 'annotation': {
119
+ base.label = event.label
120
+ if (event.data !== undefined) base.data = event.data
121
+ break
122
122
  }
123
123
  }
124
124
 
125
- return base;
125
+ return base
126
126
  }
127
127
 
128
128
  function cborToEvent(obj: Record<string, unknown>): TraceEvent {
129
- const eventType = INT_TO_EVENT_TYPE[obj.e as number];
129
+ const eventType = INT_TO_EVENT_TYPE[obj.e as number]
130
130
  if (eventType == null) {
131
131
  // Unknown event type — skip per spec (return annotation as fallback)
132
132
  return {
133
- type: "annotation",
133
+ type: 'annotation',
134
134
  seq: Number(obj.n ?? 0),
135
135
  timestamp: Number(obj.t ?? 0),
136
136
  label: `unknown-event-${obj.e}`,
137
137
  data: obj,
138
- };
138
+ }
139
139
  }
140
140
 
141
- const seq = Number(obj.n ?? 0);
142
- const timestamp = Number(obj.t ?? 0);
141
+ const seq = Number(obj.n ?? 0)
142
+ const timestamp = Number(obj.t ?? 0)
143
143
 
144
144
  switch (eventType) {
145
- case "control":
145
+ case 'control':
146
146
  return {
147
- type: "control" as const,
147
+ type: 'control' as const,
148
148
  seq,
149
149
  timestamp,
150
150
  direction: obj.d as 0 | 1,
151
151
  messageType: Number(obj.mt ?? 0),
152
152
  message: (obj.msg ?? {}) as Record<string, unknown>,
153
153
  ...(obj.raw != null ? { raw: obj.raw as Uint8Array } : {}),
154
- };
154
+ }
155
155
 
156
- case "stream-opened":
156
+ case 'stream-opened':
157
157
  return {
158
- type: "stream-opened" as const,
158
+ type: 'stream-opened' as const,
159
159
  seq,
160
160
  timestamp,
161
161
  streamId: BigInt(obj.sid as bigint | number),
162
162
  direction: obj.d as 0 | 1,
163
163
  streamType: obj.st as 0 | 1 | 2,
164
- };
164
+ }
165
165
 
166
- case "stream-closed":
166
+ case 'stream-closed':
167
167
  return {
168
- type: "stream-closed" as const,
168
+ type: 'stream-closed' as const,
169
169
  seq,
170
170
  timestamp,
171
171
  streamId: BigInt(obj.sid as bigint | number),
172
172
  errorCode: Number(obj.ec ?? 0),
173
- };
173
+ }
174
174
 
175
- case "object-header":
175
+ case 'object-header':
176
176
  return {
177
- type: "object-header" as const,
177
+ type: 'object-header' as const,
178
178
  seq,
179
179
  timestamp,
180
180
  streamId: BigInt(obj.sid as bigint | number),
@@ -182,11 +182,11 @@ function cborToEvent(obj: Record<string, unknown>): TraceEvent {
182
182
  objectId: BigInt(obj.o as bigint | number),
183
183
  publisherPriority: Number(obj.pp ?? 0),
184
184
  objectStatus: Number(obj.os ?? 0),
185
- };
185
+ }
186
186
 
187
- case "object-payload":
187
+ case 'object-payload':
188
188
  return {
189
- type: "object-payload" as const,
189
+ type: 'object-payload' as const,
190
190
  seq,
191
191
  timestamp,
192
192
  streamId: BigInt(obj.sid as bigint | number),
@@ -194,72 +194,75 @@ function cborToEvent(obj: Record<string, unknown>): TraceEvent {
194
194
  objectId: BigInt(obj.o as bigint | number),
195
195
  size: Number(obj.sz ?? 0),
196
196
  ...(obj.pl != null ? { payload: obj.pl as Uint8Array } : {}),
197
- };
197
+ }
198
198
 
199
- case "state-change":
199
+ case 'state-change':
200
200
  return {
201
- type: "state-change" as const,
201
+ type: 'state-change' as const,
202
202
  seq,
203
203
  timestamp,
204
204
  from: obj.from as string,
205
205
  to: obj.to as string,
206
- };
206
+ }
207
207
 
208
- case "error":
208
+ case 'error':
209
209
  return {
210
- type: "error" as const,
210
+ type: 'error' as const,
211
211
  seq,
212
212
  timestamp,
213
213
  errorCode: Number(obj.ec ?? 0),
214
- reason: (obj.reason ?? "") as string,
215
- };
214
+ reason: (obj.reason ?? '') as string,
215
+ }
216
216
 
217
- case "annotation":
217
+ case 'annotation':
218
218
  return {
219
- type: "annotation" as const,
219
+ type: 'annotation' as const,
220
220
  seq,
221
221
  timestamp,
222
- label: (obj.label ?? "") as string,
222
+ label: (obj.label ?? '') as string,
223
223
  data: obj.data,
224
- };
224
+ }
225
225
  }
226
226
  }
227
227
 
228
228
  // --- Preamble helpers ---
229
229
 
230
230
  function writePreamble(headerCbor: Uint8Array): Uint8Array {
231
- const buf = new Uint8Array(PREAMBLE_SIZE + headerCbor.length);
232
- buf.set(MAGIC, 0);
233
- const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
234
- view.setUint32(8, FORMAT_VERSION, true);
235
- view.setUint32(12, headerCbor.length, true);
236
- buf.set(headerCbor, PREAMBLE_SIZE);
237
- return buf;
231
+ const buf = new Uint8Array(PREAMBLE_SIZE + headerCbor.length)
232
+ buf.set(MAGIC, 0)
233
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)
234
+ view.setUint32(8, FORMAT_VERSION, true)
235
+ view.setUint32(12, headerCbor.length, true)
236
+ buf.set(headerCbor, PREAMBLE_SIZE)
237
+ return buf
238
238
  }
239
239
 
240
- function validatePreamble(bytes: Uint8Array): { version: number; headerLength: number } {
240
+ function validatePreamble(bytes: Uint8Array): {
241
+ version: number
242
+ headerLength: number
243
+ } {
241
244
  if (bytes.length < PREAMBLE_SIZE) {
242
- throw new Error("File too short: expected at least 16 bytes");
245
+ throw new Error('File too short: expected at least 16 bytes')
243
246
  }
244
247
 
245
248
  for (let i = 0; i < 8; i++) {
246
249
  if (bytes[i] !== MAGIC[i]) {
247
- throw new Error("Invalid magic bytes: not a .moqtrace file");
250
+ throw new Error('Invalid magic bytes: not a .moqtrace file')
248
251
  }
249
252
  }
250
253
 
251
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
252
- const version = view.getUint32(8, true);
254
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
255
+ const version = view.getUint32(8, true)
253
256
  if (version !== FORMAT_VERSION) {
254
- throw new Error(`Unsupported format version: ${version} (expected ${FORMAT_VERSION})`);
257
+ throw new Error(`Unsupported format version: ${version} (expected ${FORMAT_VERSION})`)
255
258
  }
256
259
 
257
- const headerLength = view.getUint32(12, true);
260
+ const headerLength = view.getUint32(12, true)
258
261
  if (PREAMBLE_SIZE + headerLength > bytes.length) {
259
- throw new Error("File truncated: header extends beyond file");
262
+ throw new Error('File truncated: header extends beyond file')
260
263
  }
261
264
 
262
- return { version, headerLength };
265
+ return { version, headerLength }
263
266
  }
264
267
 
265
268
  // --- Public API ---
@@ -268,56 +271,56 @@ function validatePreamble(bytes: Uint8Array): { version: number; headerLength: n
268
271
  * Write a complete trace to .moqtrace binary format.
269
272
  */
270
273
  export function writeMoqtrace(trace: Trace): Uint8Array {
271
- const headerCbor = encode(headerToCbor(trace.header));
272
- const preamble = writePreamble(headerCbor);
274
+ const headerCbor = encode(headerToCbor(trace.header))
275
+ const preamble = writePreamble(headerCbor)
273
276
 
274
- const eventChunks: Uint8Array[] = [];
275
- let totalEventBytes = 0;
277
+ const eventChunks: Uint8Array[] = []
278
+ let totalEventBytes = 0
276
279
  for (const event of trace.events) {
277
- const chunk = encode(eventToCbor(event));
278
- eventChunks.push(chunk);
279
- totalEventBytes += chunk.length;
280
+ const chunk = encode(eventToCbor(event))
281
+ eventChunks.push(chunk)
282
+ totalEventBytes += chunk.length
280
283
  }
281
284
 
282
- const result = new Uint8Array(preamble.length + totalEventBytes);
283
- result.set(preamble, 0);
284
- let offset = preamble.length;
285
+ const result = new Uint8Array(preamble.length + totalEventBytes)
286
+ result.set(preamble, 0)
287
+ let offset = preamble.length
285
288
  for (const chunk of eventChunks) {
286
- result.set(chunk, offset);
287
- offset += chunk.length;
289
+ result.set(chunk, offset)
290
+ offset += chunk.length
288
291
  }
289
292
 
290
- return result;
293
+ return result
291
294
  }
292
295
 
293
296
  /**
294
297
  * Read a complete .moqtrace file from bytes.
295
298
  */
296
299
  export function readMoqtrace(bytes: Uint8Array): Trace {
297
- const { headerLength } = validatePreamble(bytes);
300
+ const { headerLength } = validatePreamble(bytes)
298
301
 
299
- const headerBytes = bytes.slice(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength);
300
- const header = cborToHeader(decode(headerBytes) as Record<string, unknown>);
302
+ const headerBytes = bytes.slice(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength)
303
+ const header = cborToHeader(decode(headerBytes) as Record<string, unknown>)
301
304
 
302
- const eventBytes = bytes.slice(PREAMBLE_SIZE + headerLength);
303
- const events: TraceEvent[] = [];
305
+ const eventBytes = bytes.slice(PREAMBLE_SIZE + headerLength)
306
+ const events: TraceEvent[] = []
304
307
 
305
308
  if (eventBytes.length > 0) {
306
309
  decodeMultiple(eventBytes, (obj: unknown) => {
307
- events.push(cborToEvent(obj as Record<string, unknown>));
308
- });
310
+ events.push(cborToEvent(obj as Record<string, unknown>))
311
+ })
309
312
  }
310
313
 
311
- return { header, events };
314
+ return { header, events }
312
315
  }
313
316
 
314
317
  /**
315
318
  * Read only the header from a .moqtrace file (fast metadata peek).
316
319
  */
317
320
  export function readMoqtraceHeader(bytes: Uint8Array): TraceHeader {
318
- const { headerLength } = validatePreamble(bytes);
319
- const headerBytes = bytes.slice(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength);
320
- return cborToHeader(decode(headerBytes) as Record<string, unknown>);
321
+ const { headerLength } = validatePreamble(bytes)
322
+ const headerBytes = bytes.slice(PREAMBLE_SIZE, PREAMBLE_SIZE + headerLength)
323
+ return cborToHeader(decode(headerBytes) as Record<string, unknown>)
321
324
  }
322
325
 
323
326
  /**
@@ -325,21 +328,21 @@ export function readMoqtraceHeader(bytes: Uint8Array): TraceHeader {
325
328
  */
326
329
  export interface MoqtraceWriter {
327
330
  /** Returns the file preamble (magic + version + header). Write this first. */
328
- preamble(): Uint8Array;
331
+ preamble(): Uint8Array
329
332
  /** Encode a single event. Append the returned bytes after the preamble. */
330
- writeEvent(event: TraceEvent): Uint8Array;
333
+ writeEvent(event: TraceEvent): Uint8Array
331
334
  }
332
335
 
333
336
  export function createMoqtraceWriter(header: TraceHeader): MoqtraceWriter {
334
- const headerCbor = encode(headerToCbor(header));
335
- const preambleBytes = writePreamble(headerCbor);
337
+ const headerCbor = encode(headerToCbor(header))
338
+ const preambleBytes = writePreamble(headerCbor)
336
339
 
337
340
  return {
338
341
  preamble(): Uint8Array {
339
- return preambleBytes;
342
+ return preambleBytes
340
343
  },
341
344
  writeEvent(event: TraceEvent): Uint8Array {
342
- return encode(eventToCbor(event));
345
+ return encode(eventToCbor(event))
343
346
  },
344
- };
347
+ }
345
348
  }
package/src/index.ts CHANGED
@@ -1,17 +1,17 @@
1
1
  // Binary .moqtrace format
2
2
 
3
- export type { MoqtraceWriter } from "./binary.js";
3
+ export type { MoqtraceWriter } from './binary.js'
4
4
  export {
5
5
  createMoqtraceWriter,
6
6
  readMoqtrace,
7
7
  readMoqtraceHeader,
8
8
  writeMoqtrace,
9
- } from "./binary.js";
9
+ } from './binary.js'
10
10
  // JSON (convenience)
11
- export { traceToJSON } from "./json.js";
12
- export type { TraceRecorder } from "./recorder.js";
11
+ export { traceToJSON } from './json.js'
12
+ export type { TraceRecorder } from './recorder.js'
13
13
  // Recorder
14
- export { createRecorder } from "./recorder.js";
14
+ export { createRecorder } from './recorder.js'
15
15
 
16
16
  // Types
17
17
  export type {
@@ -29,4 +29,4 @@ export type {
29
29
  TraceErrorEvent,
30
30
  TraceEvent,
31
31
  TraceHeader,
32
- } from "./types.js";
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.
@@ -12,16 +12,16 @@ export function traceToJSON(trace: Trace): string {
12
12
  return JSON.stringify(
13
13
  trace,
14
14
  (_key, value) => {
15
- if (typeof value === "bigint") {
16
- return `0x${value.toString(16)}`;
15
+ if (typeof value === 'bigint') {
16
+ return `0x${value.toString(16)}`
17
17
  }
18
18
  if (value instanceof Uint8Array) {
19
19
  return Array.from(value)
20
- .map((b) => b.toString(16).padStart(2, "0"))
21
- .join("");
20
+ .map((b) => b.toString(16).padStart(2, '0'))
21
+ .join('')
22
22
  }
23
- return value;
23
+ return value
24
24
  },
25
25
  2,
26
- );
26
+ )
27
27
  }