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