@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/recorder.ts CHANGED
@@ -1,19 +1,20 @@
1
- import type { MoqtMessage } from "@moqtap/codec";
2
- import type { SessionState } from "@moqtap/codec/session";
3
- import type { DetailLevel, RecorderOptions, Trace, TraceEvent, TraceHeader } from "./types.js";
1
+ import type { SessionState } from '@moqtap/codec/session'
2
+ import type { DetailLevel, RecorderOptions, Trace, TraceEvent, TraceHeader } from './types.js'
4
3
 
5
4
  export interface TraceRecorder {
6
5
  /** Wrap a SessionState to auto-record control messages and state changes. */
7
- wrapSession(session: SessionState): SessionState;
6
+ wrapSession<M extends { type: string }, T extends string>(
7
+ session: SessionState<M, T>,
8
+ ): SessionState<M, T>
8
9
 
9
10
  /** Record an arbitrary event manually. */
10
- record(event: TraceEvent): void;
11
+ record(event: TraceEvent): void
11
12
 
12
13
  /** Record a stream-opened event. Ignored at 'control' detail level. */
13
- recordStreamOpened(streamId: bigint, direction: 0 | 1, streamType: 0 | 1 | 2): void;
14
+ recordStreamOpened(streamId: bigint, direction: 0 | 1, streamType: 0 | 1 | 2): void
14
15
 
15
16
  /** Record a stream-closed event. Ignored at 'control' detail level. */
16
- recordStreamClosed(streamId: bigint, errorCode?: number): void;
17
+ recordStreamClosed(streamId: bigint, errorCode?: number): void
17
18
 
18
19
  /** Record an object header event. Ignored below 'headers' detail level. */
19
20
  recordObjectHeader(
@@ -22,7 +23,7 @@ export interface TraceRecorder {
22
23
  objectId: bigint,
23
24
  publisherPriority: number,
24
25
  objectStatus: number,
25
- ): void;
26
+ ): void
26
27
 
27
28
  /** Record an object payload event. Ignored below 'headers+sizes' detail level. */
28
29
  recordObjectPayload(
@@ -31,134 +32,136 @@ export interface TraceRecorder {
31
32
  objectId: bigint,
32
33
  size: number,
33
34
  payload?: Uint8Array,
34
- ): void;
35
+ ): void
35
36
 
36
37
  /** Record a protocol error. */
37
- recordError(errorCode: number, reason: string): void;
38
+ recordError(errorCode: number, reason: string): void
38
39
 
39
40
  /** Record a user-defined annotation. */
40
- annotate(label: string, data?: unknown): void;
41
+ annotate(label: string, data?: unknown): void
41
42
 
42
43
  /** Finalize the trace. Stops recording and returns the trace. */
43
- finalize(): Trace;
44
+ finalize(): Trace
44
45
 
45
46
  /** Whether the recorder is still accepting events. */
46
- readonly recording: boolean;
47
+ readonly recording: boolean
47
48
  }
48
49
 
49
50
  const DETAIL_RANK: Record<DetailLevel, number> = {
50
51
  control: 0,
51
52
  headers: 1,
52
- "headers+sizes": 2,
53
- "headers+data": 3,
53
+ 'headers+sizes': 2,
54
+ 'headers+data': 3,
54
55
  full: 4,
55
- };
56
+ }
56
57
 
57
58
  export function createRecorder(options: RecorderOptions): TraceRecorder {
58
- const detail = options.detail;
59
- const detailRank = DETAIL_RANK[detail];
60
- const maxEvents = options.maxEvents ?? 100_000;
61
- const clock = options.clock ?? (() => Math.round(performance.now() * 1000));
62
- const messageTypeId = options.messageTypeId ?? (() => 0);
59
+ const detail = options.detail
60
+ const detailRank = DETAIL_RANK[detail]
61
+ const maxEvents = options.maxEvents ?? 100_000
62
+ const clock = options.clock ?? (() => Math.round(performance.now() * 1000))
63
+ const messageTypeId = options.messageTypeId ?? (() => 0)
63
64
 
64
- const events: TraceEvent[] = [];
65
- let _recording = true;
66
- let _seq = 0;
67
- const startTime = Date.now();
65
+ const events: TraceEvent[] = []
66
+ let _recording = true
67
+ let _seq = 0
68
+ const startTime = Date.now()
68
69
 
69
70
  function addEvent(event: TraceEvent): void {
70
- if (!_recording) return;
71
+ if (!_recording) return
71
72
  if (events.length >= maxEvents) {
72
- events.shift();
73
+ events.shift()
73
74
  }
74
- events.push(event);
75
+ events.push(event)
75
76
  }
76
77
 
77
78
  function nextSeq(): number {
78
- return _seq++;
79
+ return _seq++
79
80
  }
80
81
 
81
- function wrapSession(session: SessionState): SessionState {
82
+ function wrapSession<M extends { type: string }, T extends string>(
83
+ session: SessionState<M, T>,
84
+ ): SessionState<M, T> {
82
85
  return {
83
86
  get phase() {
84
- return session.phase;
87
+ return session.phase
85
88
  },
86
89
  get role() {
87
- return session.role;
90
+ return session.role
88
91
  },
89
92
  get subscriptions() {
90
- return session.subscriptions;
93
+ return session.subscriptions
91
94
  },
92
95
  get announces() {
93
- return session.announces;
96
+ return session.announces
94
97
  },
95
98
  get legalOutgoing() {
96
- return session.legalOutgoing;
99
+ return session.legalOutgoing
97
100
  },
98
101
  get legalIncoming() {
99
- return session.legalIncoming;
102
+ return session.legalIncoming
100
103
  },
101
104
 
102
- receive(message: MoqtMessage) {
103
- const prevPhase = session.phase;
104
- const result = session.receive(message);
105
+ receive(message: M) {
106
+ const prevPhase = session.phase
107
+ const result = session.receive(message)
105
108
 
106
109
  addEvent({
107
- type: "control",
110
+ type: 'control',
108
111
  seq: nextSeq(),
109
112
  timestamp: clock(),
110
113
  direction: 1, // rx
111
114
  messageType: messageTypeId(message.type),
112
115
  message: message as unknown as Record<string, unknown>,
113
- });
116
+ })
114
117
 
115
118
  if (result.ok && result.phase !== prevPhase) {
116
119
  addEvent({
117
- type: "state-change",
120
+ type: 'state-change',
118
121
  seq: nextSeq(),
119
122
  timestamp: clock(),
120
123
  from: prevPhase,
121
124
  to: result.phase,
122
- });
125
+ })
123
126
  }
124
127
 
125
- return result;
128
+ return result
126
129
  },
127
130
 
128
- validateOutgoing(message: MoqtMessage) {
129
- return session.validateOutgoing(message);
131
+ validateOutgoing(message: M) {
132
+ return session.validateOutgoing(message)
130
133
  },
131
134
 
132
- send(message: MoqtMessage) {
133
- const prevPhase = session.phase;
134
- const result = session.send(message);
135
+ send(message: M) {
136
+ const prevPhase = session.phase
137
+ const result = session.send(message)
135
138
 
136
139
  addEvent({
137
- type: "control",
140
+ type: 'control',
138
141
  seq: nextSeq(),
139
142
  timestamp: clock(),
140
143
  direction: 0, // tx
141
144
  messageType: messageTypeId(message.type),
142
145
  message: message as unknown as Record<string, unknown>,
143
- });
146
+ })
144
147
 
145
148
  if (result.ok && result.phase !== prevPhase) {
146
149
  addEvent({
147
- type: "state-change",
150
+ type: 'state-change',
148
151
  seq: nextSeq(),
149
152
  timestamp: clock(),
150
153
  from: prevPhase,
151
154
  to: result.phase,
152
- });
155
+ })
153
156
  }
154
157
 
155
- return result;
158
+ return result
156
159
  },
157
160
 
158
161
  reset() {
159
- session.reset();
162
+ session.reset()
160
163
  },
161
- };
164
+ }
162
165
  }
163
166
 
164
167
  return {
@@ -167,32 +170,32 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
167
170
  record: addEvent,
168
171
 
169
172
  recordStreamOpened(streamId, direction, streamType) {
170
- if (detailRank < DETAIL_RANK.headers) return;
173
+ if (detailRank < DETAIL_RANK.headers) return
171
174
  addEvent({
172
- type: "stream-opened",
175
+ type: 'stream-opened',
173
176
  seq: nextSeq(),
174
177
  timestamp: clock(),
175
178
  streamId,
176
179
  direction,
177
180
  streamType,
178
- });
181
+ })
179
182
  },
180
183
 
181
184
  recordStreamClosed(streamId, errorCode = 0) {
182
- if (detailRank < DETAIL_RANK.headers) return;
185
+ if (detailRank < DETAIL_RANK.headers) return
183
186
  addEvent({
184
- type: "stream-closed",
187
+ type: 'stream-closed',
185
188
  seq: nextSeq(),
186
189
  timestamp: clock(),
187
190
  streamId,
188
191
  errorCode,
189
- });
192
+ })
190
193
  },
191
194
 
192
195
  recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus) {
193
- if (detailRank < DETAIL_RANK.headers) return;
196
+ if (detailRank < DETAIL_RANK.headers) return
194
197
  addEvent({
195
- type: "object-header",
198
+ type: 'object-header',
196
199
  seq: nextSeq(),
197
200
  timestamp: clock(),
198
201
  streamId,
@@ -200,46 +203,46 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
200
203
  objectId,
201
204
  publisherPriority,
202
205
  objectStatus,
203
- });
206
+ })
204
207
  },
205
208
 
206
209
  recordObjectPayload(streamId, groupId, objectId, size, payload) {
207
- if (detailRank < DETAIL_RANK["headers+sizes"]) return;
210
+ if (detailRank < DETAIL_RANK['headers+sizes']) return
208
211
  const event: TraceEvent = {
209
- type: "object-payload",
212
+ type: 'object-payload',
210
213
  seq: nextSeq(),
211
214
  timestamp: clock(),
212
215
  streamId,
213
216
  groupId,
214
217
  objectId,
215
218
  size,
216
- ...(detailRank >= DETAIL_RANK["headers+data"] && payload != null ? { payload } : {}),
217
- };
218
- addEvent(event);
219
+ ...(detailRank >= DETAIL_RANK['headers+data'] && payload != null ? { payload } : {}),
220
+ }
221
+ addEvent(event)
219
222
  },
220
223
 
221
224
  recordError(errorCode, reason) {
222
225
  addEvent({
223
- type: "error",
226
+ type: 'error',
224
227
  seq: nextSeq(),
225
228
  timestamp: clock(),
226
229
  errorCode,
227
230
  reason,
228
- });
231
+ })
229
232
  },
230
233
 
231
234
  annotate(label, data) {
232
235
  addEvent({
233
- type: "annotation",
236
+ type: 'annotation',
234
237
  seq: nextSeq(),
235
238
  timestamp: clock(),
236
239
  label,
237
240
  data,
238
- });
241
+ })
239
242
  },
240
243
 
241
244
  finalize(): Trace {
242
- _recording = false;
245
+ _recording = false
243
246
  const header: TraceHeader = {
244
247
  protocol: options.protocol,
245
248
  perspective: options.perspective,
@@ -250,12 +253,12 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
250
253
  ...(options.source != null ? { source: options.source } : {}),
251
254
  ...(options.endpoint != null ? { endpoint: options.endpoint } : {}),
252
255
  ...(options.sessionId != null ? { sessionId: options.sessionId } : {}),
253
- };
254
- return { header, events: [...events] };
256
+ }
257
+ return { header, events: [...events] }
255
258
  },
256
259
 
257
260
  get recording() {
258
- return _recording;
261
+ return _recording
259
262
  },
260
- };
263
+ }
261
264
  }
package/src/types.ts CHANGED
@@ -1,79 +1,79 @@
1
- export type DetailLevel = "control" | "headers" | "headers+sizes" | "headers+data" | "full";
2
- export type Perspective = "client" | "server" | "observer";
1
+ export type DetailLevel = 'control' | 'headers' | 'headers+sizes' | 'headers+data' | 'full'
2
+ export type Perspective = 'client' | 'server' | 'observer'
3
3
 
4
4
  export interface TraceHeader {
5
- readonly protocol: string;
6
- readonly perspective: Perspective;
7
- readonly detail: DetailLevel;
8
- readonly startTime: number;
9
- readonly endTime?: number;
10
- readonly transport?: string;
11
- readonly source?: string;
12
- readonly endpoint?: string;
13
- readonly sessionId?: string;
14
- readonly custom?: Record<string, unknown>;
5
+ readonly protocol: string
6
+ readonly perspective: Perspective
7
+ readonly detail: DetailLevel
8
+ readonly startTime: number
9
+ readonly endTime?: number
10
+ readonly transport?: string
11
+ readonly source?: string
12
+ readonly endpoint?: string
13
+ readonly sessionId?: string
14
+ readonly custom?: Record<string, unknown>
15
15
  }
16
16
 
17
17
  interface BaseEvent {
18
- readonly seq: number;
19
- readonly timestamp: number;
18
+ readonly seq: number
19
+ readonly timestamp: number
20
20
  }
21
21
 
22
22
  export interface ControlMessageEvent extends BaseEvent {
23
- readonly type: "control";
24
- readonly direction: 0 | 1;
25
- readonly messageType: number;
26
- readonly message: Record<string, unknown>;
27
- readonly raw?: Uint8Array;
23
+ readonly type: 'control'
24
+ readonly direction: 0 | 1
25
+ readonly messageType: number
26
+ readonly message: Record<string, unknown>
27
+ readonly raw?: Uint8Array
28
28
  }
29
29
 
30
30
  export interface StreamOpenedEvent extends BaseEvent {
31
- readonly type: "stream-opened";
32
- readonly streamId: bigint;
33
- readonly direction: 0 | 1;
34
- readonly streamType: 0 | 1 | 2;
31
+ readonly type: 'stream-opened'
32
+ readonly streamId: bigint
33
+ readonly direction: 0 | 1
34
+ readonly streamType: 0 | 1 | 2
35
35
  }
36
36
 
37
37
  export interface StreamClosedEvent extends BaseEvent {
38
- readonly type: "stream-closed";
39
- readonly streamId: bigint;
40
- readonly errorCode: number;
38
+ readonly type: 'stream-closed'
39
+ readonly streamId: bigint
40
+ readonly errorCode: number
41
41
  }
42
42
 
43
43
  export interface ObjectHeaderEvent extends BaseEvent {
44
- readonly type: "object-header";
45
- readonly streamId: bigint;
46
- readonly groupId: bigint;
47
- readonly objectId: bigint;
48
- readonly publisherPriority: number;
49
- readonly objectStatus: number;
44
+ readonly type: 'object-header'
45
+ readonly streamId: bigint
46
+ readonly groupId: bigint
47
+ readonly objectId: bigint
48
+ readonly publisherPriority: number
49
+ readonly objectStatus: number
50
50
  }
51
51
 
52
52
  export interface ObjectPayloadEvent extends BaseEvent {
53
- readonly type: "object-payload";
54
- readonly streamId: bigint;
55
- readonly groupId: bigint;
56
- readonly objectId: bigint;
57
- readonly size: number;
58
- readonly payload?: Uint8Array;
53
+ readonly type: 'object-payload'
54
+ readonly streamId: bigint
55
+ readonly groupId: bigint
56
+ readonly objectId: bigint
57
+ readonly size: number
58
+ readonly payload?: Uint8Array
59
59
  }
60
60
 
61
61
  export interface StateChangeEvent extends BaseEvent {
62
- readonly type: "state-change";
63
- readonly from: string;
64
- readonly to: string;
62
+ readonly type: 'state-change'
63
+ readonly from: string
64
+ readonly to: string
65
65
  }
66
66
 
67
67
  export interface TraceErrorEvent extends BaseEvent {
68
- readonly type: "error";
69
- readonly errorCode: number;
70
- readonly reason: string;
68
+ readonly type: 'error'
69
+ readonly errorCode: number
70
+ readonly reason: string
71
71
  }
72
72
 
73
73
  export interface AnnotationEvent extends BaseEvent {
74
- readonly type: "annotation";
75
- readonly label: string;
76
- readonly data: unknown;
74
+ readonly type: 'annotation'
75
+ readonly label: string
76
+ readonly data: unknown
77
77
  }
78
78
 
79
79
  export type TraceEvent =
@@ -84,23 +84,23 @@ export type TraceEvent =
84
84
  | ObjectPayloadEvent
85
85
  | StateChangeEvent
86
86
  | TraceErrorEvent
87
- | AnnotationEvent;
87
+ | AnnotationEvent
88
88
 
89
89
  export interface Trace {
90
- readonly header: TraceHeader;
91
- readonly events: TraceEvent[];
90
+ readonly header: TraceHeader
91
+ readonly events: TraceEvent[]
92
92
  }
93
93
 
94
94
  export interface RecorderOptions {
95
- readonly detail: DetailLevel;
96
- readonly protocol: string;
97
- readonly perspective: Perspective;
98
- readonly transport?: string;
99
- readonly source?: string;
100
- readonly endpoint?: string;
101
- readonly sessionId?: string;
102
- readonly maxEvents?: number;
103
- readonly clock?: () => number;
95
+ readonly detail: DetailLevel
96
+ readonly protocol: string
97
+ readonly perspective: Perspective
98
+ readonly transport?: string
99
+ readonly source?: string
100
+ readonly endpoint?: string
101
+ readonly sessionId?: string
102
+ readonly maxEvents?: number
103
+ readonly clock?: () => number
104
104
  /** Map message type name (e.g. 'subscribe') to wire ID (e.g. 0x03). Required for session-layer recording. */
105
- readonly messageTypeId?: (name: string) => number;
105
+ readonly messageTypeId?: (name: string) => number
106
106
  }