@lesomnus/grpc-dgram 0.0.1 → 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.
@@ -11,6 +11,8 @@ function resolveRxConfig(c = {}) {
11
11
  policy: c.policy ?? 0
12
12
  };
13
13
  }
14
+ const DEFAULT_MAX_PEER_WINDOW = 1024;
15
+ const MAX_PEER_WINDOW = 4294967295;
14
16
  function resolveLimits(l = {}) {
15
17
  const pos = (v, d) => v !== void 0 && v > 0 ? v : d;
16
18
  return {
@@ -19,7 +21,8 @@ function resolveLimits(l = {}) {
19
21
  maxDeadPeers: pos(l.maxDeadPeers, 4),
20
22
  maxPendingResets: pos(l.maxPendingResets, 1024),
21
23
  maxLiveCalls: pos(l.maxLiveCalls, 4096),
22
- maxRepliesPerRTI: pos(l.maxRepliesPerRTI, 64)
24
+ maxRepliesPerRTI: pos(l.maxRepliesPerRTI, 64),
25
+ maxPeerWindow: Math.min(Math.max(Math.floor(pos(l.maxPeerWindow, DEFAULT_MAX_PEER_WINDOW)), DEFAULT_MAX_PEER_WINDOW), MAX_PEER_WINDOW)
23
26
  };
24
27
  }
25
28
  //#endregion
@@ -94,7 +97,7 @@ function decodeBase64(s) {
94
97
  return out;
95
98
  }
96
99
  const textEncoder$1 = new TextEncoder();
97
- const textDecoder$1 = new TextDecoder();
100
+ const textDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
98
101
  function encodeMetadataValue(key, value) {
99
102
  if (!isBinaryKey(key)) return textEncoder$1.encode(value);
100
103
  try {
@@ -104,7 +107,7 @@ function encodeMetadataValue(key, value) {
104
107
  }
105
108
  }
106
109
  function decodeMetadataValue(key, bytes) {
107
- return isBinaryKey(key) ? encodeBase64(bytes) : textDecoder$1.decode(bytes);
110
+ return isBinaryKey(key) ? encodeBase64(bytes) : textDecoder.decode(bytes);
108
111
  }
109
112
  function validateMetadata(md) {
110
113
  if (md === void 0) return;
@@ -209,12 +212,13 @@ var FrameQueue = class {
209
212
  return true;
210
213
  }
211
214
  putDrop(f, policy) {
212
- if (this.tryPut(f)) return;
215
+ if (this.tryPut(f)) return 0;
213
216
  if (policy === 1) {
214
217
  if (this.buf.shift() !== void 0) this.dropped++;
215
- if (this.tryPut(f)) return;
218
+ if (this.tryPut(f)) return 1;
216
219
  }
217
220
  this.dropped++;
221
+ return 1;
218
222
  }
219
223
  async putBlocking(f, done, signal) {
220
224
  const prev = this.putTail;
@@ -280,6 +284,7 @@ var Sweeper = class {
280
284
  }
281
285
  };
282
286
  const W_INIT = 32;
287
+ const W_CONN = 1024;
283
288
  const DEFAULT_STALL_MS = 3e4;
284
289
  function reliableRxSize(size, reliable) {
285
290
  return reliable && size < 32 ? 32 : size;
@@ -305,14 +310,31 @@ var FlowSender = class {
305
310
  }
306
311
  wake(this.waiters);
307
312
  }
313
+ reassume(window) {
314
+ this.on = window > 0;
315
+ this.observed = false;
316
+ this.granted = Math.max(window, 0);
317
+ this.sent = 0;
318
+ wake(this.waiters);
319
+ }
308
320
  grant(n) {
309
321
  if (n <= 0 || !this.on) return;
310
322
  this.granted = Math.min(this.granted + n, Number.MAX_SAFE_INTEGER);
311
323
  wake(this.waiters);
312
324
  }
313
325
  undo() {
314
- if (this.sent > 0) this.sent--;
326
+ return this.refund(1) === 1;
327
+ }
328
+ refund(n) {
329
+ const r = Math.min(n, this.sent);
330
+ this.sent -= r;
315
331
  wake(this.waiters);
332
+ return r;
333
+ }
334
+ drain() {
335
+ const n = this.sent;
336
+ this.sent = 0;
337
+ return n;
316
338
  }
317
339
  release() {
318
340
  this.on = false;
@@ -323,32 +345,45 @@ var FlowSender = class {
323
345
  this.sent++;
324
346
  return true;
325
347
  }
348
+ empty() {
349
+ return this.on && this.sent >= this.granted;
350
+ }
351
+ state() {
352
+ return {
353
+ on: this.on,
354
+ observed: this.observed,
355
+ granted: this.granted,
356
+ sent: this.sent
357
+ };
358
+ }
359
+ restore(st) {
360
+ this.on = st.on;
361
+ this.observed = st.observed;
362
+ this.granted = st.granted;
363
+ this.sent = st.sent;
364
+ }
326
365
  async acquire(done, stallMs, signal) {
327
366
  let timer;
328
367
  let dispose = noop;
329
- let expired = false;
330
368
  let bounds;
331
369
  try {
332
370
  for (;;) {
333
- if (this.tryAcquire()) return "ok";
334
371
  if (done.tripped) return "ended";
335
372
  if (signal?.aborted) return "aborted";
336
- if (expired) return "stalled";
373
+ if (this.tryAcquire()) return "ok";
337
374
  if (bounds === void 0) {
338
- bounds = [done.wait()];
375
+ bounds = [done.wait().then(() => "ended")];
339
376
  if (stallMs > 0) bounds.push(new Promise((res) => {
340
- const t = setTimeout(() => {
341
- expired = true;
342
- res();
343
- }, stallMs);
377
+ const t = setTimeout(() => res("stalled"), stallMs);
344
378
  unrefTimer(t);
345
379
  timer = t;
346
380
  }));
347
381
  if (signal !== void 0) bounds.push(new Promise((res) => {
348
- dispose = abortListener(signal, res);
382
+ dispose = abortListener(signal, () => res("aborted"));
349
383
  }));
350
384
  }
351
- await Promise.race([this.parked(), ...bounds]);
385
+ const why = await Promise.race([this.parked().then(() => "grant"), ...bounds]);
386
+ if (why !== "grant") return why;
352
387
  }
353
388
  } finally {
354
389
  if (timer !== void 0) clearTimeout(timer);
@@ -359,6 +394,51 @@ var FlowSender = class {
359
394
  return new Promise((res) => this.waiters.push(res));
360
395
  }
361
396
  };
397
+ function takeBoth(stream, peer) {
398
+ if (!stream.tryAcquire()) return "stream";
399
+ if (peer !== void 0 && !peer.tryAcquire()) {
400
+ stream.undo();
401
+ return "peer";
402
+ }
403
+ return "ok";
404
+ }
405
+ function tryAcquireBoth(stream, peer) {
406
+ return takeBoth(stream, peer) === "ok";
407
+ }
408
+ async function acquireBoth(stream, peer, done, stallMs, signal, onStall) {
409
+ let timer;
410
+ let dispose = noop;
411
+ let bounds;
412
+ try {
413
+ for (;;) {
414
+ if (done.tripped) return "ended";
415
+ if (signal?.aborted) return "aborted";
416
+ const short = takeBoth(stream, peer);
417
+ if (short === "ok") return "ok";
418
+ const onPeer = short === "peer" || peer !== void 0 && peer.empty();
419
+ if (bounds === void 0) {
420
+ bounds = [done.wait().then(() => "ended")];
421
+ if (stallMs > 0) bounds.push(new Promise((res) => {
422
+ const t = setTimeout(() => res("stalled"), stallMs);
423
+ unrefTimer(t);
424
+ timer = t;
425
+ }));
426
+ if (signal !== void 0) bounds.push(new Promise((res) => {
427
+ dispose = abortListener(signal, () => res("aborted"));
428
+ }));
429
+ onStall?.(onPeer);
430
+ }
431
+ const wait = short === "peer" ? peer.parked() : stream.parked();
432
+ const why = await Promise.race([wait.then(() => "grant"), ...bounds]);
433
+ if (why === "grant") continue;
434
+ if (why === "stalled") return onPeer ? "peer-stalled" : "stalled";
435
+ return why;
436
+ }
437
+ } finally {
438
+ if (timer !== void 0) clearTimeout(timer);
439
+ dispose();
440
+ }
441
+ }
362
442
  var FlowReceiver = class {
363
443
  on = false;
364
444
  window = 0;
@@ -379,6 +459,81 @@ var FlowReceiver = class {
379
459
  return grant;
380
460
  }
381
461
  };
462
+ const U32_MAX = 4294967295;
463
+ var PeerFlowRx = class {
464
+ window = 0;
465
+ outstanding = 0;
466
+ pending = /* @__PURE__ */ new Map();
467
+ evicted = /* @__PURE__ */ new Map();
468
+ evictCap = 0;
469
+ enable(window, evictCap) {
470
+ this.window = Math.min(window, U32_MAX);
471
+ this.evictCap = evictCap;
472
+ }
473
+ get active() {
474
+ return this.window > 0;
475
+ }
476
+ admit() {
477
+ if (this.window <= 0) return true;
478
+ if (this.outstanding >= this.window) return false;
479
+ this.outstanding++;
480
+ return true;
481
+ }
482
+ retire(epoch, n, credit) {
483
+ if (this.window <= 0) return 0;
484
+ this.outstanding -= Math.min(n, this.outstanding);
485
+ if (!credit) return 0;
486
+ this.hold(epoch, n);
487
+ return this.due(epoch);
488
+ }
489
+ unadmitted(epoch, n) {
490
+ if (this.window <= 0) return 0;
491
+ this.hold(epoch, n);
492
+ return this.due(epoch);
493
+ }
494
+ unadmittedEvicted(epoch, n) {
495
+ if (this.window <= 0 || !this.evicted.has(epoch)) return 0;
496
+ this.hold(epoch, n);
497
+ return this.due(epoch);
498
+ }
499
+ hold(epoch, n) {
500
+ this.pending.set(epoch, Math.min((this.pending.get(epoch) ?? 0) + n, U32_MAX));
501
+ }
502
+ due(epoch) {
503
+ const pending = this.pending.get(epoch) ?? 0;
504
+ if (pending === 0) return 0;
505
+ if (pending * 2 < this.window && this.outstanding + pending < this.window) return 0;
506
+ this.pending.delete(epoch);
507
+ return pending;
508
+ }
509
+ renew() {
510
+ this.pending.clear();
511
+ }
512
+ stash(epoch, state) {
513
+ if (this.evictCap <= 0) {
514
+ this.pending.delete(epoch);
515
+ return;
516
+ }
517
+ this.evicted.set(epoch, { ...state });
518
+ while (this.evicted.size > this.evictCap) {
519
+ const oldest = this.evicted.keys().next().value;
520
+ this.evicted.delete(oldest);
521
+ this.pending.delete(oldest);
522
+ }
523
+ }
524
+ unstash(epoch) {
525
+ const e = this.evicted.get(epoch);
526
+ if (e === void 0) return void 0;
527
+ this.evicted.delete(epoch);
528
+ return e;
529
+ }
530
+ creditEvicted(epoch, n) {
531
+ if (n <= 0) return;
532
+ const e = this.evicted.get(epoch);
533
+ if (e === void 0 || !e.on) return;
534
+ e.granted = Math.min(e.granted + n, Number.MAX_SAFE_INTEGER);
535
+ }
536
+ };
382
537
  const DEFAULT_MAX_RECV_MSG_SIZE = 4 * 1024 * 1024;
383
538
  const DEFAULT_MAX_SEND_MSG_SIZE = 2147483647;
384
539
  function sizeOr(v, def) {
@@ -515,6 +670,7 @@ function frame(init) {
515
670
  peerEpoch: 0,
516
671
  window: 0,
517
672
  compressor: "",
673
+ connWindow: 0,
518
674
  ...init
519
675
  };
520
676
  }
@@ -559,7 +715,10 @@ function resetFor(f) {
559
715
  });
560
716
  }
561
717
  const textEncoder = new TextEncoder();
562
- const textDecoder = new TextDecoder();
718
+ const strictTextDecoder = new TextDecoder("utf-8", {
719
+ fatal: true,
720
+ ignoreBOM: true
721
+ });
563
722
  var Writer = class {
564
723
  buf = [];
565
724
  byte(b) {
@@ -641,7 +800,12 @@ var Reader = class {
641
800
  return out;
642
801
  }
643
802
  string() {
644
- return textDecoder.decode(this.bytes());
803
+ const b = this.bytes();
804
+ try {
805
+ return strictTextDecoder.decode(b);
806
+ } catch {
807
+ throw new Error("drpc: malformed frame: string field is not valid UTF-8");
808
+ }
645
809
  }
646
810
  skip(wire) {
647
811
  switch (wire) {
@@ -717,11 +881,9 @@ function encodeMetadata(md) {
717
881
  const w = new Writer();
718
882
  for (const key of Object.keys(md).sort()) {
719
883
  const entry = new Writer();
720
- for (const v of md[key]) entry.bytes(1, encodeMetadataValue(key, v));
721
- const kv = new Writer();
722
- kv.string(1, key);
723
- kv.bytes(2, entry.finish());
724
- w.bytes(1, kv.finish());
884
+ entry.string(1, key);
885
+ for (const v of md[key]) entry.bytes(2, encodeMetadataValue(key, v));
886
+ w.bytes(1, entry.finish());
725
887
  }
726
888
  return w.finish();
727
889
  }
@@ -731,22 +893,24 @@ function decodeMetadata(data) {
731
893
  while (!r.eof) {
732
894
  const tag = r.varint32();
733
895
  if (tag >>> 3 === 1 && (tag & 7) === 2) {
734
- const kv = new Reader(r.bytes());
896
+ const entry = new Reader(r.bytes());
735
897
  let key = "";
736
898
  const raw = [];
737
- while (!kv.eof) {
738
- const t = kv.varint32();
739
- if (t >>> 3 === 1 && (t & 7) === 2) key = kv.string();
740
- else if (t >>> 3 === 2 && (t & 7) === 2) {
741
- const entry = new Reader(kv.bytes());
742
- while (!entry.eof) {
743
- const et = entry.varint32();
744
- if (et >>> 3 === 1 && (et & 7) === 2) raw.push(entry.bytes());
745
- else entry.skip(et & 7);
746
- }
747
- } else kv.skip(t & 7);
899
+ while (!entry.eof) {
900
+ const t = entry.varint32();
901
+ if (t >>> 3 === 1 && (t & 7) === 2) key = entry.string();
902
+ else if (t >>> 3 === 2 && (t & 7) === 2) raw.push(entry.bytes());
903
+ else entry.skip(t & 7);
748
904
  }
749
- md[key] = raw.map((b) => decodeMetadataValue(key, b));
905
+ const vals = raw.map((b) => decodeMetadataValue(key, b));
906
+ const prev = Object.prototype.hasOwnProperty.call(md, key) ? md[key] : void 0;
907
+ if (prev === void 0) Object.defineProperty(md, key, {
908
+ value: vals,
909
+ enumerable: true,
910
+ writable: true,
911
+ configurable: true
912
+ });
913
+ else for (const v of vals) prev.push(v);
750
914
  } else r.skip(tag & 7);
751
915
  }
752
916
  return md;
@@ -778,6 +942,10 @@ function encodeFrame(f) {
778
942
  }
779
943
  if (f.compressor) w.string(16, f.compressor);
780
944
  if (f.details !== void 0) for (const d of f.details) w.bytes(17, encodeAny(d));
945
+ if (f.connWindow) {
946
+ w.tag(18, 0);
947
+ w.varint(f.connWindow);
948
+ }
781
949
  return w.finish();
782
950
  }
783
951
  function decodeFrame(data) {
@@ -852,17 +1020,21 @@ function decodeFrame(data) {
852
1020
  if (wire !== 2) r.skip(wire);
853
1021
  else (f.details ??= []).push(decodeAny(r.bytes()));
854
1022
  break;
1023
+ case 18:
1024
+ if (wire !== 0) r.skip(wire);
1025
+ else f.connWindow = r.varint32();
1026
+ break;
855
1027
  default: r.skip(wire);
856
1028
  }
857
1029
  }
858
1030
  return f;
859
1031
  }
860
- function encodeEnvelop(frames) {
1032
+ function encodeEnvelope(frames) {
861
1033
  const w = new Writer();
862
1034
  for (const f of frames) w.bytes(1, encodeFrame(f));
863
1035
  return w.finish();
864
1036
  }
865
- function decodeEnvelop(data) {
1037
+ function decodeEnvelope(data) {
866
1038
  const frames = [];
867
1039
  const r = new Reader(data);
868
1040
  while (!r.eof) {
@@ -873,4 +1045,4 @@ function decodeEnvelop(data) {
873
1045
  return frames;
874
1046
  }
875
1047
  //#endregion
876
- export { unpack as $, DEFAULT_MAX_SEND_MSG_SIZE as A, checkSendSize as B, isTerminal as C, setFrameError as D, resetFor as E, Latch as F, noop as G, decompressPayload as H, Sweeper as I, reliableRxSize as J, nowMs as K, W_INIT as L, FlowReceiver as M, FlowSender as N, shapeOf as O, FrameQueue as P, hasTransportInfo as Q, abortListener as R, isReset as S, legalShape as T, getCompressor as U, compressPayload as V, nonzeroEpoch as W, unrefTimer as X, sizeOr as Y, hasConnAttacher as Z, isData as _, FlagReset as a, validateMetadata as at, isOpen as b, decodeEnvelop as c, resolveLimits as ct, encodeFrame as d, cloneMetadata as et, frame as f, isCompressed as g, isClose as h, FlagPing as i, metadataJoin as it, DEFAULT_STALL_MS as j, DEFAULT_MAX_RECV_MSG_SIZE as k, decodeFrame as l, resolveRxConfig as lt, hasUnknownFlags as m, FlagCompressed as n, encodeBase64 as nt, FlagWindow as o, validateMetadataPair as ot, frameStatus as p, rawPayload as q, FlagOpen as r, isBinaryKey as rt, SHAPE_MASK as s, DropPolicy as st, FlagClose as t, decodeBase64 as tt, encodeEnvelop as u, isHalfClose as v, isWindow as w, isPing as x, isHeaderFrame as y, checkRecvSize as z };
1048
+ export { sizeOr as $, DEFAULT_MAX_SEND_MSG_SIZE as A, W_INIT as B, isTerminal as C, setFrameError as D, resetFor as E, Latch as F, compressPayload as G, acquireBoth as H, PeerFlowRx as I, nonzeroEpoch as J, decompressPayload as K, Sweeper as L, FlowReceiver as M, FlowSender as N, shapeOf as O, FrameQueue as P, reliableRxSize as Q, U32_MAX as R, isReset as S, legalShape as T, checkRecvSize as U, abortListener as V, checkSendSize as W, nowMs as X, noop as Y, rawPayload as Z, isData as _, FlagReset as a, cloneMetadata as at, isOpen as b, decodeEnvelope as c, isBinaryKey as ct, encodeFrame as d, validateMetadataPair as dt, tryAcquireBoth as et, frame as f, DropPolicy as ft, isCompressed as g, isClose as h, FlagPing as i, unpack as it, DEFAULT_STALL_MS as j, DEFAULT_MAX_RECV_MSG_SIZE as k, decodeFrame as l, metadataJoin as lt, hasUnknownFlags as m, resolveRxConfig as mt, FlagCompressed as n, hasConnAttacher as nt, FlagWindow as o, decodeBase64 as ot, frameStatus as p, resolveLimits as pt, getCompressor as q, FlagOpen as r, hasTransportInfo as rt, SHAPE_MASK as s, encodeBase64 as st, FlagClose as t, unrefTimer as tt, encodeEnvelope as u, validateMetadata as ut, isHalfClose as v, isWindow as w, isPing as x, isHeaderFrame as y, W_CONN as z };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@lesomnus/grpc-dgram",
3
3
  "type": "module",
4
- "version": "0.0.1",
5
- "description": "gRPC over unreliable datagram channels \u2014 TypeScript port of gRPC-dgram (dRPC wire protocol v1.1)",
4
+ "version": "0.1.0",
5
+ "description": "gRPC over unreliable datagram channels \u2014 TypeScript port of gRPC-dgram (the dRPC wire protocol, docs/PROTOCOL.md)",
6
6
  "license": "Apache-2.0",
7
7
  "author": "lesomnus",
8
8
  "homepage": "https://github.com/lesomnus/grpc-dgram/tree/main/ts",
@@ -20,6 +20,7 @@
20
20
  "datagram",
21
21
  "webrtc",
22
22
  "websocket",
23
+ "webtransport",
23
24
  "wasm",
24
25
  "connect"
25
26
  ],
@@ -37,10 +38,12 @@
37
38
  "./transport/node-udp": "./dist/transport/node-udp.mjs",
38
39
  "./transport/protobuf-es": "./dist/transport/protobuf-es.mjs",
39
40
  "./transport/connect": "./dist/transport/connect.mjs",
41
+ "./transport/webtransport": "./dist/transport/webtransport.mjs",
40
42
  "./package.json": "./package.json"
41
43
  },
42
44
  "files": [
43
- "dist"
45
+ "dist",
46
+ "CHANGELOG.md"
44
47
  ],
45
48
  "scripts": {
46
49
  "build": "tsdown",