@moqtap/trace 0.2.0 → 0.4.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/README.md +184 -1
- package/SPEC.md +1083 -122
- package/dist/index.cjs +915 -97
- package/dist/index.d.cts +794 -9
- package/dist/index.d.ts +794 -9
- package/dist/index.js +914 -96
- package/package.json +6 -4
- package/src/binary.ts +1178 -97
- package/src/cbor-scan.ts +185 -0
- package/src/index.ts +25 -3
- package/src/json.ts +7 -0
- package/src/reader.ts +286 -0
- package/src/recorder.ts +199 -12
- package/src/types.ts +460 -3
package/src/types.ts
CHANGED
|
@@ -1,29 +1,218 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Any string other than the ones named beside it.
|
|
3
|
+
*
|
|
4
|
+
* Unions built with this keep editor completion for the values this version
|
|
5
|
+
* knows, while still accepting a value it does not. Nothing in the format
|
|
6
|
+
* rejects an unrecognised string: new perspectives, detail levels and drop
|
|
7
|
+
* policies may be added without a version bump, and a reader that refused one
|
|
8
|
+
* would refuse a file it can otherwise read in full.
|
|
9
|
+
*/
|
|
10
|
+
type OtherString = string & Record<never, never>
|
|
11
|
+
|
|
12
|
+
export type DetailLevel =
|
|
13
|
+
| 'control'
|
|
14
|
+
| 'headers'
|
|
15
|
+
| 'headers+sizes'
|
|
16
|
+
| 'headers+data'
|
|
17
|
+
| 'full'
|
|
18
|
+
| OtherString
|
|
19
|
+
|
|
20
|
+
export type Perspective = 'client' | 'server' | 'observer' | 'relay-tap' | OtherString
|
|
21
|
+
|
|
22
|
+
/** Drop strategy a sampled source applied when it could not keep up. */
|
|
23
|
+
export type DropPolicy = 'head' | 'tail' | 'sampled' | OtherString
|
|
24
|
+
|
|
25
|
+
/** Length of a trace ID, in bytes. Fixed by the format. */
|
|
26
|
+
export const TRACE_ID_LENGTH = 16
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Per-segment metadata.
|
|
30
|
+
*
|
|
31
|
+
* Present only in segmented traces. A single-shot file must not carry it —
|
|
32
|
+
* its absence is what tells a reader that sequence numbers and timestamps are
|
|
33
|
+
* file-global rather than segment-local.
|
|
34
|
+
*/
|
|
35
|
+
export interface SegmentInfo {
|
|
36
|
+
/** 0-based sequence number of this segment within the stream. */
|
|
37
|
+
readonly sequence: number
|
|
38
|
+
/** Nominal segment duration in milliseconds. A hint; the real one may differ. */
|
|
39
|
+
readonly durationMs?: number
|
|
40
|
+
/** Opaque identifier shared by every segment of the same logical stream. */
|
|
41
|
+
readonly streamId?: string
|
|
42
|
+
/** True if this segment continues a previous one with the same `streamId`. */
|
|
43
|
+
readonly continues?: boolean
|
|
44
|
+
/**
|
|
45
|
+
* Keys in the `"segment"` map this version does not recognise, kept verbatim
|
|
46
|
+
* and written back inside that map rather than at the top level.
|
|
47
|
+
*
|
|
48
|
+
* See {@link TraceHeader.extra}, which this mirrors — including the part
|
|
49
|
+
* about a key this version *does* know whose value it cannot use.
|
|
50
|
+
*/
|
|
51
|
+
readonly extra?: Record<string, unknown>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sampling and filtering metadata. Present only when events were dropped or
|
|
56
|
+
* filtered at the source; its absence means the trace is complete relative to
|
|
57
|
+
* its detail level.
|
|
58
|
+
*/
|
|
59
|
+
export interface SamplingInfo {
|
|
60
|
+
/** Fraction of source events retained, in (0, 1]. */
|
|
61
|
+
readonly effectiveRate?: number
|
|
62
|
+
/** Per-segment cap that triggered drops, if rate-limited. */
|
|
63
|
+
readonly maxEventsPerSec?: number
|
|
64
|
+
/** Drop strategy applied when the cap was exceeded. */
|
|
65
|
+
readonly dropPolicy?: DropPolicy
|
|
66
|
+
/** Cumulative events dropped since `startTime`. */
|
|
67
|
+
readonly droppedTotal?: number
|
|
68
|
+
/** Events dropped within this segment only. */
|
|
69
|
+
readonly droppedSegment?: number
|
|
70
|
+
/** Source-side filter rule that selected events. */
|
|
71
|
+
readonly rule?: string
|
|
72
|
+
/** Language the rule is written in ('prefix', 'glob', 'cel'). */
|
|
73
|
+
readonly ruleLang?: string
|
|
74
|
+
/**
|
|
75
|
+
* Event type IDs the drop policy was applied to.
|
|
76
|
+
*
|
|
77
|
+
* All or nothing: an array with one element that is not an event type ID is
|
|
78
|
+
* unusable *entire* and stays in {@link extra}. Keeping the elements that
|
|
79
|
+
* read cleanly would not be a partial answer but a wrong one — this key
|
|
80
|
+
* names the types the policy touched, and every type absent from it may be
|
|
81
|
+
* treated as complete, so a shortened array reports a sampled type as fully
|
|
82
|
+
* recorded.
|
|
83
|
+
*/
|
|
84
|
+
readonly appliesTo?: number[]
|
|
85
|
+
/**
|
|
86
|
+
* Keys in the `"sampling"` map this version does not recognise, kept
|
|
87
|
+
* verbatim and written back inside that map rather than at the top level.
|
|
88
|
+
*
|
|
89
|
+
* See {@link TraceHeader.extra}, which this mirrors.
|
|
90
|
+
*/
|
|
91
|
+
readonly extra?: Record<string, unknown>
|
|
92
|
+
}
|
|
3
93
|
|
|
4
94
|
export interface TraceHeader {
|
|
5
95
|
readonly protocol: string
|
|
6
96
|
readonly perspective: Perspective
|
|
7
97
|
readonly detail: DetailLevel
|
|
98
|
+
/**
|
|
99
|
+
* Recording start (Unix epoch milliseconds). In a segmented trace this is
|
|
100
|
+
* the segment's start, not the stream's.
|
|
101
|
+
*/
|
|
8
102
|
readonly startTime: number
|
|
9
103
|
readonly endTime?: number
|
|
10
104
|
readonly transport?: string
|
|
11
105
|
readonly source?: string
|
|
12
106
|
readonly endpoint?: string
|
|
13
107
|
readonly sessionId?: string
|
|
108
|
+
/** Present only when this header begins one segment of a segmented stream. */
|
|
109
|
+
readonly segment?: SegmentInfo
|
|
110
|
+
/** Present only when events were dropped or filtered at the source. */
|
|
111
|
+
readonly sampling?: SamplingInfo
|
|
112
|
+
/**
|
|
113
|
+
* User-defined metadata, handed back key for key and value for value.
|
|
114
|
+
*
|
|
115
|
+
* A passthrough, and the one map in the header with no store of its own:
|
|
116
|
+
* every key in it belongs to whoever wrote the trace, so there is no such
|
|
117
|
+
* thing as an unrecognised key there. A `"custom"` this reader cannot hold
|
|
118
|
+
* exactly — one that is not a map at all — is therefore unusable as a whole
|
|
119
|
+
* and goes to {@link extra}, leaving this field absent rather than typed as
|
|
120
|
+
* a record it is not.
|
|
121
|
+
*/
|
|
14
122
|
readonly custom?: Record<string, unknown>
|
|
123
|
+
/**
|
|
124
|
+
* Keys in the header map that this version of the package does not
|
|
125
|
+
* recognise, kept verbatim.
|
|
126
|
+
*
|
|
127
|
+
* The same guarantee a `TraceEvent`'s `extra` gives one level down, and for
|
|
128
|
+
* the same reason: "unknown keys MUST be ignored" is a rule about reading
|
|
129
|
+
* *past* them, not a licence to drop them. A tool that reads a trace and
|
|
130
|
+
* writes it back — a redaction pass, a filter, a re-segmentation, a download
|
|
131
|
+
* with annotations applied — would otherwise emit a valid file that looks as
|
|
132
|
+
* though it never carried them, and one tool's ignorance would become
|
|
133
|
+
* permanent for every reader downstream of it.
|
|
134
|
+
*
|
|
135
|
+
* A key this version *does* know counts as unrecognised when its value is of
|
|
136
|
+
* a type the field cannot hold. `"transport": 42` is not a transport; it is
|
|
137
|
+
* also not nothing. Such a value is kept here rather than coerced into the
|
|
138
|
+
* field or dropped, {@link transport} reads as absent, and the key is
|
|
139
|
+
* written back unchanged. Knowing more about a key must not mean preserving
|
|
140
|
+
* it less.
|
|
141
|
+
*
|
|
142
|
+
* Three maps in the header carry a store — this one, {@link SegmentInfo} and
|
|
143
|
+
* {@link SamplingInfo} — and each is written back into the map it came from.
|
|
144
|
+
* One store for the whole header would not do: a private key on `"segment"`
|
|
145
|
+
* and a key of the same name at the top level are different keys, and
|
|
146
|
+
* re-emitting either in the other's map changes what the file says.
|
|
147
|
+
*/
|
|
148
|
+
readonly extra?: Record<string, unknown>
|
|
15
149
|
}
|
|
16
150
|
|
|
17
151
|
interface BaseEvent {
|
|
152
|
+
/**
|
|
153
|
+
* Monotonically increasing sequence number. Segment-local in a segmented
|
|
154
|
+
* trace, so ordering across segments is `(segment.sequence, seq)`.
|
|
155
|
+
*/
|
|
18
156
|
readonly seq: number
|
|
157
|
+
/** Microseconds since the containing segment's `startTime`. */
|
|
19
158
|
readonly timestamp: number
|
|
159
|
+
/**
|
|
160
|
+
* Which peer this event pertains to.
|
|
161
|
+
*
|
|
162
|
+
* Required when the perspective is `'relay-tap'`, where one trace covers
|
|
163
|
+
* many concurrent sessions; omitted otherwise. The identifier is
|
|
164
|
+
* source-local: the same string in two traces from different sources does
|
|
165
|
+
* not name the same peer.
|
|
166
|
+
*/
|
|
167
|
+
readonly peer?: string
|
|
168
|
+
/**
|
|
169
|
+
* Keys on this event that this version of the package does not recognise,
|
|
170
|
+
* kept verbatim.
|
|
171
|
+
*
|
|
172
|
+
* Optional keys may be added to an existing event type without a format
|
|
173
|
+
* version bump, so "unknown keys MUST be ignored" is a rule about *reading
|
|
174
|
+
* past* them. It is not a licence to drop them: a tool that reads a trace
|
|
175
|
+
* and writes it back — a redaction pass, a filter, an annotated download —
|
|
176
|
+
* would otherwise emit a valid file that looks like it never carried them,
|
|
177
|
+
* and one tool's ignorance would become permanent for every reader
|
|
178
|
+
* downstream of it.
|
|
179
|
+
*
|
|
180
|
+
* A key this version *does* know counts as unrecognised when its value is of
|
|
181
|
+
* a type the field cannot hold — a `"ta"` carrying text, say. It is kept
|
|
182
|
+
* here rather than coerced into the field or dropped, and written back
|
|
183
|
+
* unchanged, exactly as a key nobody had heard of would be. Knowing more
|
|
184
|
+
* about a key must not mean preserving it less.
|
|
185
|
+
*
|
|
186
|
+
* {@link UnknownEvent} already does this for an event type the package
|
|
187
|
+
* cannot name. This is the same guarantee one level down, for a key on a
|
|
188
|
+
* type it can. It is always absent on an `UnknownEvent`, whose `fields`
|
|
189
|
+
* hold every non-common key already.
|
|
190
|
+
*/
|
|
191
|
+
readonly extra?: Record<string, unknown>
|
|
20
192
|
}
|
|
21
193
|
|
|
22
194
|
export interface ControlMessageEvent extends BaseEvent {
|
|
23
195
|
readonly type: 'control'
|
|
24
196
|
readonly direction: 0 | 1
|
|
25
197
|
readonly messageType: number
|
|
26
|
-
|
|
198
|
+
/**
|
|
199
|
+
* The message's decoded fields — the `"msg"` field of Event 0.
|
|
200
|
+
*
|
|
201
|
+
* A writer MUST produce a map here, keyed in snake_case (`request_id`,
|
|
202
|
+
* `track_alias`), and an empty one when it decoded nothing. A *reader* has
|
|
203
|
+
* to accept less, because files written before the format said so exist:
|
|
204
|
+
* every `capture-*` case in the conformance corpus carries a Rust `Debug`
|
|
205
|
+
* rendering of the message instead — `'Draft16(ClientSetup(ClientSetup {
|
|
206
|
+
* parameters: [] }))'` — and the format requires such a value to be handed
|
|
207
|
+
* back unchanged rather than rejected or replaced.
|
|
208
|
+
*
|
|
209
|
+
* So the type is `unknown`, not `Record<string, unknown>`. The `Record` was
|
|
210
|
+
* a lie the compiler could not catch: `event.message.request_id` typechecks
|
|
211
|
+
* against a string and reads `undefined` at runtime, on exactly the files
|
|
212
|
+
* this rule exists for. Narrow before reading keys — {@link
|
|
213
|
+
* controlMessageFields} does it correctly.
|
|
214
|
+
*/
|
|
215
|
+
readonly message: unknown
|
|
27
216
|
readonly raw?: Uint8Array
|
|
28
217
|
/**
|
|
29
218
|
* QUIC stream the message travelled on.
|
|
@@ -36,11 +225,85 @@ export interface ControlMessageEvent extends BaseEvent {
|
|
|
36
225
|
readonly streamId?: bigint
|
|
37
226
|
}
|
|
38
227
|
|
|
228
|
+
/**
|
|
229
|
+
* The decoded fields of a control message, or `undefined` when its `"msg"` is
|
|
230
|
+
* not a map and there are no fields to address.
|
|
231
|
+
*
|
|
232
|
+
* Provided because the narrowing is easy to get wrong once per call site and
|
|
233
|
+
* silently: `typeof message === 'object'` also admits `null`, a CBOR array,
|
|
234
|
+
* and a byte string, each of which would then be typed as a field map and
|
|
235
|
+
* read back `undefined` for every key. A caller that skips the check entirely
|
|
236
|
+
* gets the bug this function exists to prevent — see
|
|
237
|
+
* {@link ControlMessageEvent.message}.
|
|
238
|
+
*/
|
|
239
|
+
export function controlMessageFields(message: unknown): Record<string, unknown> | undefined {
|
|
240
|
+
if (message === null || typeof message !== 'object') return undefined
|
|
241
|
+
if (Array.isArray(message) || ArrayBuffer.isView(message)) return undefined
|
|
242
|
+
return message as Record<string, unknown>
|
|
243
|
+
}
|
|
244
|
+
|
|
39
245
|
export interface StreamOpenedEvent extends BaseEvent {
|
|
40
246
|
readonly type: 'stream-opened'
|
|
41
247
|
readonly streamId: bigint
|
|
42
248
|
readonly direction: 0 | 1
|
|
43
249
|
readonly streamType: 0 | 1 | 2
|
|
250
|
+
/**
|
|
251
|
+
* Track alias the stream carries. Written whenever the recorder knows it.
|
|
252
|
+
*
|
|
253
|
+
* The stream header carries the alias and no detail level records that
|
|
254
|
+
* header's bytes, so before this field a `'headers'` recording could not say
|
|
255
|
+
* which track a stream belonged to — which is most of what the level exists
|
|
256
|
+
* to answer, and there was nothing left to re-parse it from.
|
|
257
|
+
*
|
|
258
|
+
* `bigint`, and so are the three fields below, unlike this event's
|
|
259
|
+
* `direction` and `streamType`. Those two are small enumerations that a
|
|
260
|
+
* narrow type suits; these four are wire identifiers with the same range as
|
|
261
|
+
* {@link ObjectHeaderEvent}'s `groupId`, `objectId` and `streamId`, and
|
|
262
|
+
* readers compare the two events. Typed as `number` here, a match would read
|
|
263
|
+
* as a mismatch: `streamOpened.groupId === objectHeader.groupId` evaluates
|
|
264
|
+
* `42 === 42n`, which is `false`, with no error, for every pair of values
|
|
265
|
+
* that are in fact the same.
|
|
266
|
+
*
|
|
267
|
+
* Absent when the file's `"ta"` was not an unsigned integer — and the same
|
|
268
|
+
* for the three fields below. Such a value belongs to no field here, so it
|
|
269
|
+
* stays verbatim in {@link BaseEvent.extra} and is written back as it came.
|
|
270
|
+
* It is never coerced on the way in: `true` and the text `"4"` are not track
|
|
271
|
+
* alias 1 and track alias 4, however willingly `BigInt` converts them.
|
|
272
|
+
*/
|
|
273
|
+
readonly trackAlias?: bigint
|
|
274
|
+
/**
|
|
275
|
+
* Subgroup ID, on a subgroup stream (`streamType === 0`).
|
|
276
|
+
*
|
|
277
|
+
* A writer must not set it on another stream type, where nothing supplies
|
|
278
|
+
* it. A reader that meets one there keeps it rather than rejecting the
|
|
279
|
+
* event: an out-of-scope key makes one field meaningless, not the file
|
|
280
|
+
* unreadable, and discarding the event throws away every other field on it —
|
|
281
|
+
* all of which were fine. That is about the key being in the wrong place; a
|
|
282
|
+
* `"sg"` of the wrong *shape* is not held here at all, but in
|
|
283
|
+
* {@link BaseEvent.extra}.
|
|
284
|
+
*/
|
|
285
|
+
readonly subgroupId?: bigint
|
|
286
|
+
/**
|
|
287
|
+
* Fetch request ID, on a fetch stream (`streamType === 2`).
|
|
288
|
+
*
|
|
289
|
+
* A writer MUST set it there. It is the only correlation between the stream
|
|
290
|
+
* and the FETCH that asked for it, and without it a fetch stream cannot be
|
|
291
|
+
* attributed to a request at all. A reader must nevertheless accept a fetch
|
|
292
|
+
* stream that lacks one — every recording made before the key existed lacks
|
|
293
|
+
* it — for the same reason as the `'msg'` asymmetry on
|
|
294
|
+
* {@link ControlMessageEvent.message}: writers conform, readers tolerate.
|
|
295
|
+
*/
|
|
296
|
+
readonly fetchRequestId?: bigint
|
|
297
|
+
/**
|
|
298
|
+
* Group ID, on a datagram stream (`streamType === 1`).
|
|
299
|
+
*
|
|
300
|
+
* Scoped to datagrams because on a subgroup stream every object carries the
|
|
301
|
+
* same group by construction, so a copy here would be a second field with no
|
|
302
|
+
* independent source. Where an {@link ObjectHeaderEvent} for the same stream
|
|
303
|
+
* disagrees, that event wins and this is not corruption: this copy is a
|
|
304
|
+
* convenience for a reader that has not yet seen an object.
|
|
305
|
+
*/
|
|
306
|
+
readonly groupId?: bigint
|
|
44
307
|
}
|
|
45
308
|
|
|
46
309
|
export interface StreamClosedEvent extends BaseEvent {
|
|
@@ -73,10 +336,86 @@ export interface StateChangeEvent extends BaseEvent {
|
|
|
73
336
|
readonly to: string
|
|
74
337
|
}
|
|
75
338
|
|
|
339
|
+
/**
|
|
340
|
+
* What sort of failure an error event records.
|
|
341
|
+
*
|
|
342
|
+
* An open vocabulary: this revision names the three below, others may be added
|
|
343
|
+
* without a format version bump, and a reader must preserve a value it does not
|
|
344
|
+
* recognise rather than reject the event — the same contract as {@link
|
|
345
|
+
* Perspective} and {@link DetailLevel}, which is why all three widen with
|
|
346
|
+
* `OtherString` instead of being closed unions.
|
|
347
|
+
*/
|
|
348
|
+
export type ErrorKind =
|
|
349
|
+
/** The peer violated the protocol. */
|
|
350
|
+
| 'protocol'
|
|
351
|
+
/** The QUIC or WebTransport layer failed. */
|
|
352
|
+
| 'transport'
|
|
353
|
+
/** Bytes that would not parse as any message this recorder knows. */
|
|
354
|
+
| 'decode'
|
|
355
|
+
| OtherString
|
|
356
|
+
|
|
76
357
|
export interface TraceErrorEvent extends BaseEvent {
|
|
77
358
|
readonly type: 'error'
|
|
78
359
|
readonly errorCode: number
|
|
79
360
|
readonly reason: string
|
|
361
|
+
/**
|
|
362
|
+
* QUIC stream the error was observed on.
|
|
363
|
+
*
|
|
364
|
+
* Optional on the same terms as {@link ControlMessageEvent.streamId}: absent
|
|
365
|
+
* means there was no stream, or that the recorder knew of none, which a
|
|
366
|
+
* reader MUST distinguish from stream `0`. `bigint` for the reason given
|
|
367
|
+
* there — it is the same wire identifier, and `number` stops being exact at
|
|
368
|
+
* 2^53, which would make this reader disagree with the Rust one silently and
|
|
369
|
+
* only for large values.
|
|
370
|
+
*
|
|
371
|
+
* It may name a *data* stream, which is why {@link rawLength} is gated a
|
|
372
|
+
* level above this event rather than travelling with it: the length behind an
|
|
373
|
+
* error on a subgroup stream is the size of a run of media framing.
|
|
374
|
+
*/
|
|
375
|
+
readonly streamId?: bigint
|
|
376
|
+
/**
|
|
377
|
+
* What sort of failure this was.
|
|
378
|
+
*
|
|
379
|
+
* Spelt out rather than `kind` because {@link SubscriptionDerivationEvent}
|
|
380
|
+
* already owns that name with the unrelated vocabulary `'created'` /
|
|
381
|
+
* `'shared'`. Two vocabularies under one name survive in a typed reader and
|
|
382
|
+
* break in every flat projection of a trace — a JSONL export, a warehouse
|
|
383
|
+
* load, this package's own {@link traceToJSON} — where they become one column
|
|
384
|
+
* holding both. The wire key is `"ek"` for the same reason.
|
|
385
|
+
*/
|
|
386
|
+
readonly errorKind?: ErrorKind
|
|
387
|
+
/**
|
|
388
|
+
* How many bytes the recorder held for this error, before any truncation.
|
|
389
|
+
*
|
|
390
|
+
* Not the length of {@link raw}: where the two differ the capture is partial,
|
|
391
|
+
* and this is how a reader learns how much is missing. A `raw` of exactly
|
|
392
|
+
* `MAX_ERROR_RAW_BYTES` with no `rawLength` beside it must be treated as
|
|
393
|
+
* possibly truncated, that being the one length the cap makes ambiguous.
|
|
394
|
+
*
|
|
395
|
+
* Recorded from `'headers+sizes'` up — one level below {@link raw} and
|
|
396
|
+
* deliberately not with it. It is a size and carries no content, so it is
|
|
397
|
+
* available in every trace where the bytes themselves must not appear, and
|
|
398
|
+
* how large a malformed message was often separates a truncated message from
|
|
399
|
+
* a mistyped one on its own.
|
|
400
|
+
*/
|
|
401
|
+
readonly rawLength?: number
|
|
402
|
+
/**
|
|
403
|
+
* The bytes behind the error.
|
|
404
|
+
*
|
|
405
|
+
* Payload-bearing, and recorded only at `'full'`: an error naming a data
|
|
406
|
+
* stream has subgroup framing and object payload behind it, so inheriting
|
|
407
|
+
* this event's own `control`+ level would have put media into traces whose
|
|
408
|
+
* declared level excludes payloads entirely.
|
|
409
|
+
*
|
|
410
|
+
* The 4096-byte cap on this field binds a *recorder* — see
|
|
411
|
+
* `MAX_ERROR_RAW_BYTES` in `recorder.ts`, which is where it is applied and
|
|
412
|
+
* the only place it appears. It does not bind this type, the reader or the
|
|
413
|
+
* writer: a longer value read out of a file is handed over whole and written
|
|
414
|
+
* back whole. Re-truncating it would destroy evidence to make someone else's
|
|
415
|
+
* file conform to a rule that was never addressed to the tool doing the
|
|
416
|
+
* truncating.
|
|
417
|
+
*/
|
|
418
|
+
readonly raw?: Uint8Array
|
|
80
419
|
}
|
|
81
420
|
|
|
82
421
|
export interface AnnotationEvent extends BaseEvent {
|
|
@@ -85,6 +424,110 @@ export interface AnnotationEvent extends BaseEvent {
|
|
|
85
424
|
readonly data: unknown
|
|
86
425
|
}
|
|
87
426
|
|
|
427
|
+
/** Role a peer is acting in at connection time. */
|
|
428
|
+
export type PeerRole = 'publisher' | 'subscriber' | 'both' | OtherString
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Which end of a relay a peer sits on.
|
|
432
|
+
*
|
|
433
|
+
* This is the direction the connection was made in, not the direction
|
|
434
|
+
* subscriptions flow. For subscription causality see
|
|
435
|
+
* {@link SubscriptionDerivationEvent}.
|
|
436
|
+
*/
|
|
437
|
+
export type Side = 'downstream' | 'upstream' | OtherString
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* A new peer session was established. Emitted only by `'relay-tap'` sources;
|
|
441
|
+
* the peer it identifies is on the event's `peer` field.
|
|
442
|
+
*/
|
|
443
|
+
export interface PeerConnectedEvent extends BaseEvent {
|
|
444
|
+
readonly type: 'peer-connected'
|
|
445
|
+
readonly endpoint?: string
|
|
446
|
+
readonly transport?: string
|
|
447
|
+
readonly role?: PeerRole
|
|
448
|
+
readonly side?: Side
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** A peer session ended. Emitted only by `'relay-tap'` sources. */
|
|
452
|
+
export interface PeerDisconnectedEvent extends BaseEvent {
|
|
453
|
+
readonly type: 'peer-disconnected'
|
|
454
|
+
readonly errorCode: number
|
|
455
|
+
readonly reason?: string
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* A `(peer, request id)` pair naming one subscription on one peer.
|
|
460
|
+
*
|
|
461
|
+
* Request ids are unique only within a peer's session, so neither half
|
|
462
|
+
* identifies a subscription on its own.
|
|
463
|
+
*/
|
|
464
|
+
export interface SubscriptionRef {
|
|
465
|
+
readonly peer: string
|
|
466
|
+
readonly requestId: bigint
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** How an upstream subscription came to serve a downstream one. */
|
|
470
|
+
export type DerivationKind = 'created' | 'shared' | OtherString
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* An upstream subscription was created or extended in causal response to
|
|
474
|
+
* downstream ones. Emitted only by `'relay-tap'` sources.
|
|
475
|
+
*
|
|
476
|
+
* This is the primitive multi-hop correlation is built from: a collector
|
|
477
|
+
* reconstructs an end-to-end tree from these links plus the trace ids
|
|
478
|
+
* propagated along them.
|
|
479
|
+
*
|
|
480
|
+
* The four timestamps share the `timestamp` field's timebase — the emitting
|
|
481
|
+
* source's own clock. Differences between them are meaningful with no
|
|
482
|
+
* cross-hop clock agreement, which is the point: they measure how long one
|
|
483
|
+
* hop took. Never subtract one source's timestamp from another's.
|
|
484
|
+
*
|
|
485
|
+
* A source emits the event as soon as the downstream SUBSCRIBE arrives,
|
|
486
|
+
* carrying whichever timestamps it has, and may emit it again for the same
|
|
487
|
+
* pair as the rest arrive. Treat a later event for the same pair as an update
|
|
488
|
+
* to the earlier one, not as a second derivation.
|
|
489
|
+
*/
|
|
490
|
+
export interface SubscriptionDerivationEvent extends BaseEvent {
|
|
491
|
+
readonly type: 'subscription-derivation'
|
|
492
|
+
readonly upstream: SubscriptionRef
|
|
493
|
+
/** Every downstream subscription currently served by `upstream`. */
|
|
494
|
+
readonly downstream: SubscriptionRef[]
|
|
495
|
+
readonly kind: DerivationKind
|
|
496
|
+
/**
|
|
497
|
+
* Trace id propagated along the subscription chain: exactly
|
|
498
|
+
* {@link TRACE_ID_LENGTH} raw bytes, never hex- or base64-encoded, so two
|
|
499
|
+
* implementations produce identical values for the same chain.
|
|
500
|
+
*/
|
|
501
|
+
readonly traceId?: Uint8Array
|
|
502
|
+
/** Track namespace the subscription targets, one entry per field. */
|
|
503
|
+
readonly namespace?: Uint8Array[]
|
|
504
|
+
readonly trackName?: Uint8Array
|
|
505
|
+
/** When the downstream SUBSCRIBE was received. */
|
|
506
|
+
readonly tDownstreamReceived?: number
|
|
507
|
+
/** When the upstream SUBSCRIBE was sent. Absent for a terminal subscription. */
|
|
508
|
+
readonly tUpstreamSent?: number
|
|
509
|
+
/** When SUBSCRIBE_OK arrived from upstream. */
|
|
510
|
+
readonly tUpstreamOkReceived?: number
|
|
511
|
+
/** When SUBSCRIBE_OK was sent downstream. */
|
|
512
|
+
readonly tDownstreamOkSent?: number
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* An event whose type this version of the package does not know.
|
|
517
|
+
*
|
|
518
|
+
* New event types may be added without a format version bump, so a reader
|
|
519
|
+
* that dropped or relabelled them would make one tool's ignorance permanent
|
|
520
|
+
* for everything downstream of it. The fields are kept verbatim, so an
|
|
521
|
+
* unknown event survives a read-modify-write round trip intact.
|
|
522
|
+
*/
|
|
523
|
+
export interface UnknownEvent extends BaseEvent {
|
|
524
|
+
readonly type: 'unknown'
|
|
525
|
+
/** The `"e"` discriminant that was read. */
|
|
526
|
+
readonly eventType: number
|
|
527
|
+
/** Every key from the event map other than `n`, `t`, `p` and `e`. */
|
|
528
|
+
readonly fields: Record<string, unknown>
|
|
529
|
+
}
|
|
530
|
+
|
|
88
531
|
export type TraceEvent =
|
|
89
532
|
| ControlMessageEvent
|
|
90
533
|
| StreamOpenedEvent
|
|
@@ -94,6 +537,10 @@ export type TraceEvent =
|
|
|
94
537
|
| StateChangeEvent
|
|
95
538
|
| TraceErrorEvent
|
|
96
539
|
| AnnotationEvent
|
|
540
|
+
| PeerConnectedEvent
|
|
541
|
+
| PeerDisconnectedEvent
|
|
542
|
+
| SubscriptionDerivationEvent
|
|
543
|
+
| UnknownEvent
|
|
97
544
|
|
|
98
545
|
export interface Trace {
|
|
99
546
|
readonly header: TraceHeader
|
|
@@ -108,6 +555,16 @@ export interface RecorderOptions {
|
|
|
108
555
|
readonly source?: string
|
|
109
556
|
readonly endpoint?: string
|
|
110
557
|
readonly sessionId?: string
|
|
558
|
+
/**
|
|
559
|
+
* Extra header keys to write, for a recorder that has something to say the
|
|
560
|
+
* format has no key for.
|
|
561
|
+
*
|
|
562
|
+
* Goes to {@link TraceHeader.extra} on the finalized trace and is written
|
|
563
|
+
* into the header map after the keys above. Use the `"x-"` prefix the format
|
|
564
|
+
* reserves for private use; a key the format defines is dropped here rather
|
|
565
|
+
* than written twice, since the field always wins.
|
|
566
|
+
*/
|
|
567
|
+
readonly extra?: Record<string, unknown>
|
|
111
568
|
readonly maxEvents?: number
|
|
112
569
|
readonly clock?: () => number
|
|
113
570
|
/** Map message type name (e.g. 'subscribe') to wire ID (e.g. 0x03). Required for session-layer recording. */
|