@moqtap/trace 0.1.3 → 0.3.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/README.md +95 -0
- package/SPEC.md +388 -102
- package/dist/index.cjs +463 -54
- package/dist/index.d.cts +260 -5
- package/dist/index.d.ts +260 -5
- package/dist/index.js +462 -53
- package/package.json +2 -2
- package/src/binary.ts +469 -57
- package/src/cbor-scan.ts +185 -0
- package/src/index.ts +18 -1
- package/src/recorder.ts +10 -0
- package/src/types.ts +207 -2
package/README.md
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
Record, serialize, and analyze MoQT (Media over QUIC Transport) sessions using the `.moqtrace` binary format.
|
|
4
4
|
|
|
5
5
|
- Binary `.moqtrace` format with CBOR encoding (compact, streamable, cross-language)
|
|
6
|
+
- Segmented traces for capture rotation, live carriage, and recovery from a damaged region
|
|
6
7
|
- Configurable detail levels from control-only to full wire captures
|
|
7
8
|
- Session recorder that wraps `@moqtap/codec` session state machines
|
|
8
9
|
- Human-readable JSON export for debugging
|
|
9
10
|
- Zero-copy streaming writer for large traces
|
|
10
11
|
|
|
12
|
+
Writes format version 2 and reads versions 1 and 2.
|
|
13
|
+
|
|
11
14
|
## Install
|
|
12
15
|
|
|
13
16
|
```bash
|
|
@@ -78,6 +81,66 @@ for (const event of events) {
|
|
|
78
81
|
}
|
|
79
82
|
```
|
|
80
83
|
|
|
84
|
+
### Segmented traces
|
|
85
|
+
|
|
86
|
+
A segment is a complete `.moqtrace` blob — magic, version, header, events —
|
|
87
|
+
appended after the one before it, so a reader can start at any segment without
|
|
88
|
+
the ones preceding it. That buys capture rotation without finalizing a file, a
|
|
89
|
+
natural cut point for live carriage, and recovery from a damaged region.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { readMoqtraceSegments, writeMoqtraceSegments } from '@moqtap/trace'
|
|
93
|
+
|
|
94
|
+
// Each segment header must carry `segment` metadata: it is what tells a reader
|
|
95
|
+
// that `seq` and `timestamp` restart at zero in every segment.
|
|
96
|
+
const bytes = writeMoqtraceSegments([
|
|
97
|
+
{ header: { ...header, segment: { sequence: 0 } }, events: firstBatch },
|
|
98
|
+
{ header: { ...header, segment: { sequence: 1, continues: true } }, events: secondBatch },
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
// Read the boundaries — you need them to order events across segments.
|
|
102
|
+
for (const segment of readMoqtraceSegments(bytes)) {
|
|
103
|
+
console.log(segment.header.segment?.sequence, segment.events.length)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Or ignore them: readMoqtrace flattens every segment's events in order.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Damaged and truncated files
|
|
110
|
+
|
|
111
|
+
A file that stops part-way through an event throws `TruncatedTraceError`, which
|
|
112
|
+
carries everything that decoded before the cut — a truncated trace is still
|
|
113
|
+
evidence. On a segmented trace, `{ recover: true }` skips a damaged region and
|
|
114
|
+
resumes at the next segment instead of throwing.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { readMoqtrace, readMoqtraceSegments, TruncatedTraceError } from '@moqtap/trace'
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const trace = readMoqtrace(bytes)
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof TruncatedTraceError) {
|
|
123
|
+
console.warn(`truncated at byte ${error.offset}`)
|
|
124
|
+
console.log(error.trace?.events.length, 'events survived')
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const whatSurvived = readMoqtraceSegments(bytes, { recover: true })
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Reading a trace you did not write
|
|
132
|
+
|
|
133
|
+
Nothing here rejects a file for carrying something newer than this version
|
|
134
|
+
knows. An unrecognised event type arrives as an `UnknownEvent` with its fields
|
|
135
|
+
intact, so it survives a read-modify-write round trip rather than being dropped
|
|
136
|
+
or relabelled; an unrecognised perspective, detail level, role, side, drop
|
|
137
|
+
policy or derivation kind is kept verbatim.
|
|
138
|
+
|
|
139
|
+
The one place that refuses is `createRecorder`, which cannot honour a detail
|
|
140
|
+
level it does not implement: it would either capture less than you asked for
|
|
141
|
+
while writing a header claiming otherwise, or — for the payload-bearing levels
|
|
142
|
+
— capture more.
|
|
143
|
+
|
|
81
144
|
## Detail Levels
|
|
82
145
|
|
|
83
146
|
| Level | What's recorded |
|
|
@@ -88,6 +151,10 @@ for (const event of events) {
|
|
|
88
151
|
| `headers+data` | + full payload bytes |
|
|
89
152
|
| `full` | + raw wire bytes (pre-decode) |
|
|
90
153
|
|
|
154
|
+
`headers+data` and `full`, and the `raw` field on any event, carry user
|
|
155
|
+
content. Capturing them is optional for a conformant recorder and should be an
|
|
156
|
+
explicit choice, not a default.
|
|
157
|
+
|
|
91
158
|
## JSON Export
|
|
92
159
|
|
|
93
160
|
For human-readable debugging (not lossless — bigint/Uint8Array become strings):
|
|
@@ -99,6 +166,34 @@ const json = traceToJSON(trace)
|
|
|
99
166
|
console.log(json)
|
|
100
167
|
```
|
|
101
168
|
|
|
169
|
+
## Upgrading from 0.2.0
|
|
170
|
+
|
|
171
|
+
The package now writes format version 2. A 0.2.0 reader rejects those files at
|
|
172
|
+
byte 8, which is the point of the bump: it turns what would have been silent
|
|
173
|
+
desynchronization on a segmented trace into a clear refusal. Reading is
|
|
174
|
+
unaffected in the other direction — 0.3.0 reads everything 0.2.0 wrote.
|
|
175
|
+
|
|
176
|
+
What changes for code already using it:
|
|
177
|
+
|
|
178
|
+
- `TraceEvent` gained four members (`peer-connected`, `peer-disconnected`,
|
|
179
|
+
`subscription-derivation`, `unknown`). A `switch` that handles only the
|
|
180
|
+
cases it cares about is unaffected; one that asserts exhaustiveness is not.
|
|
181
|
+
- `Perspective`, `DetailLevel` and the new string unions accept values they do
|
|
182
|
+
not name, so indexing a `Record<DetailLevel, T>` with one now yields
|
|
183
|
+
`T | undefined`.
|
|
184
|
+
- An unknown event type used to arrive as an annotation labelled
|
|
185
|
+
`unknown-event-<n>`. It now arrives as an `UnknownEvent` carrying its real
|
|
186
|
+
type and fields, so code matching that label should match `type === 'unknown'`
|
|
187
|
+
instead. The old shape was lossy in a way that mattered: writing the trace
|
|
188
|
+
back rewrote the event's type to 7.
|
|
189
|
+
- `createRecorder` throws on a detail level it does not implement, which the
|
|
190
|
+
types previously made unexpressible.
|
|
191
|
+
- The bytes changed in two ways that make files readable by the Rust
|
|
192
|
+
implementation for the first time: an integral value past 32 bits is now
|
|
193
|
+
written as a CBOR integer rather than a float64, and a byte string as major
|
|
194
|
+
type 2 rather than wrapped in the typed-array tag 64. Both readers accept
|
|
195
|
+
either form, so nothing already written becomes unreadable.
|
|
196
|
+
|
|
102
197
|
## File Format
|
|
103
198
|
|
|
104
199
|
See [SPEC.md](./SPEC.md) for the complete `.moqtrace` binary format specification.
|