@trevrpc/trevrpc-js 0.1.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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/bin/protoc-gen-trevrpc-js.js +17 -0
  4. package/examples/greeter/README.md +35 -0
  5. package/examples/greeter/client.js +172 -0
  6. package/examples/greeter/greeter.node.trevrpc.d.ts +53 -0
  7. package/examples/greeter/greeter.node.trevrpc.js +9 -0
  8. package/examples/greeter/greeter.proto +18 -0
  9. package/examples/greeter/greeter.trevrpc.d.ts +55 -0
  10. package/examples/greeter/greeter.trevrpc.js +103 -0
  11. package/examples/greeter/index.html +140 -0
  12. package/examples/greeter/static-server.js +168 -0
  13. package/package.json +89 -0
  14. package/src/advanced.d.ts +29 -0
  15. package/src/advanced.js +1 -0
  16. package/src/browser-channel.js +45 -0
  17. package/src/channel.js +375 -0
  18. package/src/client.js +1397 -0
  19. package/src/connect.js +6 -0
  20. package/src/connect.node.js +6 -0
  21. package/src/framing.js +618 -0
  22. package/src/generator.js +868 -0
  23. package/src/index.d.ts +577 -0
  24. package/src/index.js +3 -0
  25. package/src/index.node.d.ts +10 -0
  26. package/src/index.node.js +3 -0
  27. package/src/metadata.js +112 -0
  28. package/src/native-loader.js +94 -0
  29. package/src/node-advanced.d.ts +27 -0
  30. package/src/node-advanced.js +1 -0
  31. package/src/node-generated.d.ts +24 -0
  32. package/src/node-generated.js +126 -0
  33. package/src/node-index.d.ts +1 -0
  34. package/src/node-index.js +7 -0
  35. package/src/node.d.ts +298 -0
  36. package/src/node.js +1456 -0
  37. package/src/runtime.js +66 -0
  38. package/src/server-response.js +49 -0
  39. package/src/status.js +145 -0
  40. package/src/timer.js +29 -0
  41. package/src/webtransport.js +624 -0
  42. package/src/wire.js +403 -0
package/src/connect.js ADDED
@@ -0,0 +1,6 @@
1
+ import { Channel } from "./browser-channel.js";
2
+
3
+ /** Opens a TrevRPC client for the current JavaScript runtime. */
4
+ export async function connect(url, options = {}) {
5
+ return await Channel.connect(url, options);
6
+ }
@@ -0,0 +1,6 @@
1
+ import { Channel } from "./node.js";
2
+
3
+ /** Opens a TrevRPC client backed by the native Node runtime. */
4
+ export async function connect(urlOrOptions, options = {}) {
5
+ return await Channel.connect(urlOrOptions, options);
6
+ }
package/src/framing.js ADDED
@@ -0,0 +1,618 @@
1
+ import { FrameTooLargeError, invalidArgument, unavailable } from "./status.js";
2
+ import {
3
+ RpcStreamFrame,
4
+ RpcStreamFrameKind,
5
+ preflightWireMessage,
6
+ validateWireMessage,
7
+ } from "./wire.js";
8
+
9
+ export const DefaultMaxFrameSize = 4 * 1024 * 1024;
10
+
11
+ const FrameHeaderLength = 4;
12
+ const StreamFrameBodyTag = (4 << 3) | 2;
13
+ const EmptyBytes = new Uint8Array(0);
14
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
15
+
16
+ /** Encodes a protobuf message body. */
17
+ export function marshalMessage(messageType, message) {
18
+ const prepared = prepareMessage(messageType, message);
19
+ return messageType.encode(prepared).finish();
20
+ }
21
+
22
+ /** Decodes a protobuf message body. */
23
+ export function unmarshalMessage(messageType, body) {
24
+ try {
25
+ preflightWireMessage(messageType, body);
26
+ return messageType.decode(body);
27
+ } catch (error) {
28
+ const failure = invalidArgument(
29
+ `failed to decode protobuf message: ${error?.message ?? String(error)}`,
30
+ );
31
+ failure.reason = error?.reason ?? "malformed_protobuf";
32
+ failure.cause = error;
33
+ throw failure;
34
+ }
35
+ }
36
+
37
+ /** Encodes a protobuf message into a length-prefixed TrevRPC frame. */
38
+ export function encodeFrame(messageType, message, maxFrameSize = DefaultMaxFrameSize) {
39
+ const body = marshalMessage(messageType, message);
40
+ if (body.byteLength > maxFrameSize) {
41
+ throw new FrameTooLargeError(body.byteLength, maxFrameSize);
42
+ }
43
+
44
+ const frame = new Uint8Array(FrameHeaderLength + body.byteLength);
45
+ const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
46
+ view.setUint32(0, body.byteLength, false);
47
+ frame.set(body, FrameHeaderLength);
48
+ return frame;
49
+ }
50
+
51
+ /** Encodes one streaming message frame carrying an already-encoded protobuf body. */
52
+ export function encodeMessageStreamFrame(body, maxFrameSize = DefaultMaxFrameSize) {
53
+ return encodeMessageStreamFrames([body], maxFrameSize);
54
+ }
55
+
56
+ /** Encodes multiple streaming message frames into one contiguous write buffer. */
57
+ export function encodeMessageStreamFrames(
58
+ bodies,
59
+ maxFrameSize = DefaultMaxFrameSize,
60
+ start = 0,
61
+ end = undefined,
62
+ ) {
63
+ const range = bodyRange(bodies, start, end);
64
+ let totalLength = 0;
65
+ const byteBodies = Array.from({ length: range.end - range.start });
66
+ for (let index = range.start; index < range.end; index += 1) {
67
+ const body = byteView(range.bodies[index]);
68
+ byteBodies[index - range.start] = body;
69
+ const bodyLength = messageStreamFrameBodyLength(body.byteLength);
70
+ if (bodyLength > maxFrameSize) {
71
+ throw new FrameTooLargeError(bodyLength, maxFrameSize);
72
+ }
73
+ totalLength += FrameHeaderLength + bodyLength;
74
+ }
75
+
76
+ const frames = new Uint8Array(totalLength);
77
+ const view = new DataView(frames.buffer, frames.byteOffset, frames.byteLength);
78
+ let offset = 0;
79
+ for (const body of byteBodies) {
80
+ const bodyLength = messageStreamFrameBodyLength(body.byteLength);
81
+ view.setUint32(offset, bodyLength, false);
82
+ offset += FrameHeaderLength;
83
+ if (body.byteLength > 0) {
84
+ frames[offset++] = StreamFrameBodyTag;
85
+ offset = appendVarint(frames, offset, body.byteLength);
86
+ frames.set(body, offset);
87
+ offset += body.byteLength;
88
+ }
89
+ }
90
+
91
+ return frames;
92
+ }
93
+
94
+ /** Encodes each streaming message frame separately for vectored writers. */
95
+ export function encodeMessageStreamFrameParts(
96
+ bodies,
97
+ maxFrameSize = DefaultMaxFrameSize,
98
+ start = 0,
99
+ end = undefined,
100
+ ) {
101
+ const range = bodyRange(bodies, start, end);
102
+ const frames = Array.from({ length: range.end - range.start });
103
+ for (let index = range.start; index < range.end; index += 1) {
104
+ frames[index - range.start] = encodeMessageStreamFrame(range.bodies[index], maxFrameSize);
105
+ }
106
+ return frames;
107
+ }
108
+
109
+ /** Decodes a protobuf message from a TrevRPC frame body. */
110
+ export function decodeFrame(messageType, body) {
111
+ return unmarshalMessage(messageType, body);
112
+ }
113
+
114
+ /** Decodes a streaming RPC frame body using a fast path for common message frames. */
115
+ export function decodeStreamFrameBody(body) {
116
+ const bytes = byteView(body);
117
+ try {
118
+ preflightWireMessage(RpcStreamFrame, bytes);
119
+ } catch (error) {
120
+ const failure = invalidArgument(
121
+ `failed to decode protobuf message: ${error?.message ?? String(error)}`,
122
+ );
123
+ failure.reason = error?.reason ?? "malformed_protobuf";
124
+ failure.cause = error;
125
+ throw failure;
126
+ }
127
+ if (bytes.byteLength === 0) {
128
+ return streamFrameMessage(EmptyBytes);
129
+ }
130
+
131
+ const cursor = { offset: 0 };
132
+ let kind = RpcStreamFrameKind.Message;
133
+ let status = 0;
134
+ let message = "";
135
+ let frameBody = new Uint8Array(0);
136
+
137
+ while (cursor.offset < bytes.byteLength) {
138
+ const tag = consumeVarint(bytes, cursor, "invalid stream frame field tag");
139
+ if (tag === 0) {
140
+ throw invalidArgument("invalid stream frame field tag");
141
+ }
142
+
143
+ const field = Math.floor(tag / 8);
144
+ const wireType = tag % 8;
145
+ switch (field) {
146
+ case 1:
147
+ requireWireType(wireType, 0, "invalid stream frame kind wire type");
148
+ kind = consumeVarint(bytes, cursor, "truncated stream frame kind");
149
+ break;
150
+ case 2:
151
+ requireWireType(wireType, 0, "invalid stream frame status wire type");
152
+ status = consumeVarint(bytes, cursor, "truncated stream frame status");
153
+ break;
154
+ case 3:
155
+ requireWireType(wireType, 2, "invalid stream frame message wire type");
156
+ message = decodeUtf8(consumeLengthDelimited(bytes, cursor, "invalid stream frame message"));
157
+ break;
158
+ case 4:
159
+ requireWireType(wireType, 2, "invalid stream frame body wire type");
160
+ frameBody = consumeLengthDelimited(bytes, cursor, "invalid stream frame body");
161
+ break;
162
+ case 5: {
163
+ const frame = decodeFrame(RpcStreamFrame, bytes);
164
+ validateDecodedWireMessage(RpcStreamFrame, frame);
165
+ return frame;
166
+ }
167
+ default:
168
+ skipProtoField(bytes, cursor, wireType);
169
+ break;
170
+ }
171
+ }
172
+
173
+ const frame = { kind, status, message, body: frameBody, metadata: {} };
174
+ validateDecodedWireMessage(RpcStreamFrame, frame);
175
+ return frame;
176
+ }
177
+
178
+ /** Writes one length-prefixed protobuf frame. */
179
+ export async function writeFrame(writer, messageType, message, maxFrameSize = DefaultMaxFrameSize) {
180
+ await writer.write(encodeFrame(messageType, message, maxFrameSize));
181
+ }
182
+
183
+ /** Writes multiple streaming message frames in one write. */
184
+ export async function writeMessageStreamFrames(
185
+ writer,
186
+ bodies,
187
+ maxFrameSize = DefaultMaxFrameSize,
188
+ start = 0,
189
+ end = undefined,
190
+ ) {
191
+ if (typeof writer.writev === "function") {
192
+ await writer.writev(encodeMessageStreamFrameParts(bodies, maxFrameSize, start, end));
193
+ return;
194
+ }
195
+
196
+ await writer.write(encodeMessageStreamFrames(bodies, maxFrameSize, start, end));
197
+ }
198
+
199
+ /** Reads length-prefixed protobuf frames from a byte stream reader. */
200
+ export class FrameReader {
201
+ /** Creates a frame reader over a stream reader. */
202
+ constructor(reader) {
203
+ this.reader = reader;
204
+ this.chunks = [];
205
+ this.chunkHead = 0;
206
+ this.buffered = 0;
207
+ }
208
+
209
+ /** Reads and decodes one frame. */
210
+ async readFrame(messageType, maxFrameSize = DefaultMaxFrameSize) {
211
+ const header = await this.readExact(FrameHeaderLength, false);
212
+ const length = frameBodyLength(header, maxFrameSize);
213
+ const body = await this.readExact(length, false);
214
+ return decodeFrame(messageType, body);
215
+ }
216
+
217
+ /** Reads and decodes one frame, or returns null when already at EOF. */
218
+ async readFrameOrEOF(messageType, maxFrameSize = DefaultMaxFrameSize) {
219
+ const header = await this.readExact(FrameHeaderLength, true);
220
+ if (header == null) {
221
+ return null;
222
+ }
223
+
224
+ const length = frameBodyLength(header, maxFrameSize);
225
+ const body = await this.readExact(length, false);
226
+ return decodeFrame(messageType, body);
227
+ }
228
+
229
+ /** Reads and decodes one streaming RPC frame, or returns null when already at EOF. */
230
+ async readStreamFrameOrEOF(maxFrameSize = DefaultMaxFrameSize) {
231
+ const header = await this.readExact(FrameHeaderLength, true);
232
+ if (header == null) {
233
+ return null;
234
+ }
235
+
236
+ const length = frameBodyLength(header, maxFrameSize);
237
+ const body = await this.readExact(length, false);
238
+ return decodeStreamFrameBody(body);
239
+ }
240
+
241
+ /** Reads one streaming RPC frame, then drains complete frames already buffered. */
242
+ async readStreamFrameBatchOrEOF(maxBatch = 32, maxFrameSize = DefaultMaxFrameSize) {
243
+ const first = await this.readStreamFrameOrEOF(maxFrameSize);
244
+ if (first == null) {
245
+ return null;
246
+ }
247
+
248
+ const limit = Math.max(1, Math.floor(maxBatch));
249
+ const frames = [first];
250
+ while (frames.length < limit) {
251
+ const body = this.tryReadFrameBody(maxFrameSize);
252
+ if (body === undefined) {
253
+ break;
254
+ }
255
+ frames.push(decodeStreamFrameBody(body));
256
+ }
257
+ return frames;
258
+ }
259
+
260
+ /** Reads stream message body batches, with at most one terminal status. */
261
+ async readStreamMessageBodyBatchOrEOF(maxBatch = 32, maxFrameSize = DefaultMaxFrameSize) {
262
+ const body = await this.readRawFrameBodyOrEOF(maxFrameSize);
263
+ if (body == null) {
264
+ return null;
265
+ }
266
+
267
+ const limit = Math.max(1, Math.floor(maxBatch));
268
+ const result = { bodies: [], status: null };
269
+ pushStreamBodyOrStatus(result, body);
270
+ while (result.status == null && result.bodies.length < limit) {
271
+ const nextBody = this.tryReadFrameBody(maxFrameSize);
272
+ if (nextBody === undefined) {
273
+ break;
274
+ }
275
+ pushStreamBodyOrStatus(result, nextBody);
276
+ }
277
+
278
+ return result;
279
+ }
280
+
281
+ /** Reads one raw frame body, or returns null when already at EOF. */
282
+ async readRawFrameBodyOrEOF(maxFrameSize = DefaultMaxFrameSize) {
283
+ const header = await this.readExact(FrameHeaderLength, true);
284
+ if (header == null) {
285
+ return null;
286
+ }
287
+
288
+ return this.readExact(frameBodyLength(header, maxFrameSize), false);
289
+ }
290
+
291
+ /** Reads a complete buffered frame body without waiting, or undefined. */
292
+ tryReadFrameBody(maxFrameSize = DefaultMaxFrameSize) {
293
+ if (this.buffered < FrameHeaderLength) {
294
+ return undefined;
295
+ }
296
+
297
+ const header = this.peek(FrameHeaderLength);
298
+ const length = frameBodyLength(header, maxFrameSize);
299
+ if (this.buffered < FrameHeaderLength + length) {
300
+ return undefined;
301
+ }
302
+
303
+ this.consume(FrameHeaderLength);
304
+ return this.consume(length);
305
+ }
306
+
307
+ /** Reads exactly size bytes from the underlying reader. */
308
+ async readExact(size, allowEofAtStart) {
309
+ if (size === 0) {
310
+ return EmptyBytes;
311
+ }
312
+
313
+ while (this.buffered < size) {
314
+ const { value, done } = await this.reader.read();
315
+ if (done) {
316
+ if (allowEofAtStart && this.buffered === 0) {
317
+ return null;
318
+ }
319
+
320
+ const error = unavailable("transport unavailable: stream ended in the middle of a frame");
321
+ error.reason = "incomplete_frame";
322
+ throw error;
323
+ }
324
+
325
+ if (value == null || value.byteLength === 0) {
326
+ continue;
327
+ }
328
+
329
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
330
+ this.chunks.push(bytes);
331
+ this.buffered += bytes.byteLength;
332
+ }
333
+
334
+ return this.consume(size);
335
+ }
336
+
337
+ /** Removes size bytes from the buffered data. */
338
+ consume(size) {
339
+ if (size === 0) {
340
+ return EmptyBytes;
341
+ }
342
+
343
+ const first = this.chunks[this.chunkHead];
344
+ if (first != null && first.byteLength >= size) {
345
+ const result = first.subarray(0, size);
346
+ if (first.byteLength === size) {
347
+ this.chunkHead += 1;
348
+ this.compactChunks();
349
+ } else {
350
+ this.chunks[this.chunkHead] = first.subarray(size);
351
+ }
352
+ this.buffered -= size;
353
+ return result;
354
+ }
355
+
356
+ const result = new Uint8Array(size);
357
+ let offset = 0;
358
+
359
+ while (offset < size) {
360
+ const chunk = this.chunks[this.chunkHead];
361
+ const needed = size - offset;
362
+ if (chunk.byteLength <= needed) {
363
+ result.set(chunk, offset);
364
+ offset += chunk.byteLength;
365
+ this.chunkHead += 1;
366
+ } else {
367
+ result.set(chunk.subarray(0, needed), offset);
368
+ this.chunks[this.chunkHead] = chunk.subarray(needed);
369
+ offset += needed;
370
+ }
371
+ }
372
+
373
+ this.buffered -= size;
374
+ this.compactChunks();
375
+ return result;
376
+ }
377
+
378
+ /** Copies size bytes from the buffered data without removing them. */
379
+ peek(size) {
380
+ if (size === 0) {
381
+ return EmptyBytes;
382
+ }
383
+
384
+ const first = this.chunks[this.chunkHead];
385
+ if (first != null && first.byteLength >= size) {
386
+ return first.subarray(0, size);
387
+ }
388
+
389
+ const result = new Uint8Array(size);
390
+ let offset = 0;
391
+
392
+ for (let index = this.chunkHead; index < this.chunks.length; index += 1) {
393
+ const chunk = this.chunks[index];
394
+ if (offset >= size) {
395
+ break;
396
+ }
397
+ const count = Math.min(chunk.byteLength, size - offset);
398
+ result.set(chunk.subarray(0, count), offset);
399
+ offset += count;
400
+ }
401
+
402
+ return result;
403
+ }
404
+
405
+ compactChunks() {
406
+ if (this.chunkHead === 0) {
407
+ return;
408
+ }
409
+ if (this.chunkHead === this.chunks.length) {
410
+ this.chunks.length = 0;
411
+ this.chunkHead = 0;
412
+ return;
413
+ }
414
+ if (this.chunkHead >= 32 && this.chunkHead * 2 >= this.chunks.length) {
415
+ this.chunks.splice(0, this.chunkHead);
416
+ this.chunkHead = 0;
417
+ }
418
+ }
419
+ }
420
+
421
+ function pushStreamBodyOrStatus(result, body) {
422
+ const messageBody = tryDecodeStreamMessageBody(body);
423
+ if (messageBody !== undefined) {
424
+ result.bodies.push(messageBody);
425
+ return;
426
+ }
427
+
428
+ const frame = decodeStreamFrameBody(body);
429
+ switch (frame.kind) {
430
+ case RpcStreamFrameKind.Message:
431
+ result.bodies.push(frame.body ?? EmptyBytes);
432
+ break;
433
+ case RpcStreamFrameKind.Status:
434
+ result.status = frame;
435
+ break;
436
+ default:
437
+ throw invalidArgument("stream frame contained an unknown frame kind");
438
+ }
439
+ }
440
+
441
+ function tryDecodeStreamMessageBody(body) {
442
+ const bytes = byteView(body);
443
+ if (bytes.byteLength === 0) {
444
+ return EmptyBytes;
445
+ }
446
+
447
+ if (bytes[0] !== StreamFrameBodyTag) {
448
+ return undefined;
449
+ }
450
+
451
+ const cursor = { offset: 1 };
452
+ const length = consumeVarint(bytes, cursor, "invalid stream frame body");
453
+ const end = cursor.offset + length;
454
+ if (end !== bytes.byteLength) {
455
+ return undefined;
456
+ }
457
+ return bytes.subarray(cursor.offset, end);
458
+ }
459
+
460
+ /** Decodes and validates the body length stored in a TrevRPC frame header. */
461
+ export function frameBodyLength(header, maxFrameSize = DefaultMaxFrameSize) {
462
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
463
+ const length = view.getUint32(0, false);
464
+ if (length > maxFrameSize) {
465
+ throw new FrameTooLargeError(length, maxFrameSize);
466
+ }
467
+
468
+ return length;
469
+ }
470
+
471
+ function validateDecodedWireMessage(messageType, message) {
472
+ try {
473
+ validateWireMessage(messageType, message);
474
+ } catch (error) {
475
+ const failure = invalidArgument(error?.message ?? String(error));
476
+ failure.reason = error?.reason;
477
+ failure.cause = error;
478
+ throw failure;
479
+ }
480
+ }
481
+
482
+ function prepareMessage(messageType, message) {
483
+ const prepared =
484
+ typeof messageType.fromObject === "function" ? messageType.fromObject(message) : message;
485
+ const error = typeof messageType.verify === "function" ? messageType.verify(prepared) : null;
486
+ if (error != null) {
487
+ throw invalidArgument(`failed to encode protobuf message: ${error}`);
488
+ }
489
+
490
+ return prepared;
491
+ }
492
+
493
+ function streamFrameMessage(body) {
494
+ return {
495
+ kind: RpcStreamFrameKind.Message,
496
+ status: 0,
497
+ message: "",
498
+ body,
499
+ metadata: {},
500
+ };
501
+ }
502
+
503
+ function byteView(value) {
504
+ if (value instanceof Uint8Array) {
505
+ return value;
506
+ }
507
+ if (ArrayBuffer.isView(value)) {
508
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
509
+ }
510
+ return new Uint8Array(value ?? 0);
511
+ }
512
+
513
+ function bodyRange(bodies, start, end) {
514
+ const source = Array.isArray(bodies) ? bodies : Array.from(bodies);
515
+ const from = clampRangeIndex(start, source.length);
516
+ const to = Math.max(from, clampRangeIndex(end ?? source.length, source.length));
517
+ return { bodies: source, start: from, end: to };
518
+ }
519
+
520
+ function clampRangeIndex(value, length) {
521
+ if (!Number.isFinite(value)) {
522
+ return length;
523
+ }
524
+ return Math.min(length, Math.max(0, Math.floor(value)));
525
+ }
526
+
527
+ function messageStreamFrameBodyLength(bodyLength) {
528
+ return bodyLength === 0 ? 0 : 1 + varintLength(bodyLength) + bodyLength;
529
+ }
530
+
531
+ function appendVarint(output, offset, value) {
532
+ let remaining = value;
533
+ while (remaining >= 0x80) {
534
+ output[offset++] = (remaining % 0x80) | 0x80;
535
+ remaining = Math.floor(remaining / 0x80);
536
+ }
537
+ output[offset++] = remaining;
538
+ return offset;
539
+ }
540
+
541
+ function varintLength(value) {
542
+ let remaining = value;
543
+ let length = 1;
544
+ while (remaining >= 0x80) {
545
+ remaining = Math.floor(remaining / 0x80);
546
+ length += 1;
547
+ }
548
+ return length;
549
+ }
550
+
551
+ function consumeVarint(data, cursor, message) {
552
+ let value = 0;
553
+ let multiplier = 1;
554
+ for (let index = 0; index < 10; index += 1) {
555
+ if (cursor.offset >= data.byteLength) {
556
+ throw invalidArgument(message);
557
+ }
558
+ const byte = data[cursor.offset++];
559
+ value += (byte & 0x7f) * multiplier;
560
+ if (byte < 0x80) {
561
+ return value;
562
+ }
563
+ multiplier *= 0x80;
564
+ }
565
+ throw invalidArgument("stream frame varint exceeded 64 bits");
566
+ }
567
+
568
+ function requireWireType(actual, expected, message) {
569
+ if (actual !== expected) {
570
+ throw invalidArgument(message);
571
+ }
572
+ }
573
+
574
+ function consumeLengthDelimited(data, cursor, message) {
575
+ const length = consumeVarint(data, cursor, message);
576
+ const end = cursor.offset + length;
577
+ if (!Number.isSafeInteger(end) || end > data.byteLength) {
578
+ throw invalidArgument(message);
579
+ }
580
+ const value = data.subarray(cursor.offset, end);
581
+ cursor.offset = end;
582
+ return value;
583
+ }
584
+
585
+ function skipProtoField(data, cursor, wireType) {
586
+ switch (wireType) {
587
+ case 0:
588
+ consumeVarint(data, cursor, "truncated stream frame varint");
589
+ return;
590
+ case 1:
591
+ skipFixed(data, cursor, 8);
592
+ return;
593
+ case 2:
594
+ consumeLengthDelimited(data, cursor, "truncated stream frame field");
595
+ return;
596
+ case 5:
597
+ skipFixed(data, cursor, 4);
598
+ return;
599
+ default:
600
+ throw invalidArgument("stream frame contained an unsupported wire type");
601
+ }
602
+ }
603
+
604
+ function skipFixed(data, cursor, length) {
605
+ const end = cursor.offset + length;
606
+ if (end > data.byteLength) {
607
+ throw invalidArgument("truncated stream frame fixed field");
608
+ }
609
+ cursor.offset = end;
610
+ }
611
+
612
+ function decodeUtf8(bytes) {
613
+ try {
614
+ return utf8Decoder.decode(bytes);
615
+ } catch (error) {
616
+ throw invalidArgument(`stream frame message was not valid UTF-8: ${error.message}`);
617
+ }
618
+ }