@lesomnus/grpc-dgram 0.0.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/dist/index.mjs ADDED
@@ -0,0 +1,1079 @@
1
+ import { $ as unpack, A as DEFAULT_MAX_SEND_MSG_SIZE, B as checkSendSize, C as isTerminal, D as setFrameError, E as resetFor, F as Latch, G as noop, H as decompressPayload, I as Sweeper, J as reliableRxSize, K as nowMs, L as W_INIT, M as FlowReceiver, N as FlowSender, O as shapeOf, P as FrameQueue, Q as hasTransportInfo, S as isReset, T as legalShape, U as getCompressor, V as compressPayload, W as nonzeroEpoch, X as unrefTimer, Y as sizeOr, Z as hasConnAttacher, _ as isData, a as FlagReset, at as validateMetadata, b as isOpen, c as decodeEnvelop, ct as resolveLimits, d as encodeFrame, et as cloneMetadata, f as frame, g as isCompressed, h as isClose, i as FlagPing, it as metadataJoin, j as DEFAULT_STALL_MS, k as DEFAULT_MAX_RECV_MSG_SIZE, l as decodeFrame, lt as resolveRxConfig, m as hasUnknownFlags, n as FlagCompressed, nt as encodeBase64, o as FlagWindow, ot as validateMetadataPair, p as frameStatus, q as rawPayload, r as FlagOpen, rt as isBinaryKey, s as SHAPE_MASK, st as DropPolicy, t as FlagClose, tt as decodeBase64, u as encodeEnvelop, v as isHalfClose, w as isWindow, x as isPing, y as isHeaderFrame, z as checkRecvSize } from "./wire-BR8KiyRg.mjs";
2
+ import { a as isMessageTooLarge, i as abortCause, n as MessageTooLargeError, o as statusError, r as StatusError, s as toStatusError, t as Code } from "./status-DZwMDWIn.mjs";
3
+ import { a as resolveTiming, c as RxWindow, i as statusDetails, l as TxSeq, n as Conn, o as K_LOUD, r as EndOfStreamError, s as RxVerdict, t as ClientStream, u as W_FWD } from "./conn-DOmx4nbt.mjs";
4
+ //#region src/desc.ts
5
+ function unaryMethod(path, init) {
6
+ return {
7
+ path,
8
+ clientStreams: false,
9
+ serverStreams: false,
10
+ ...init
11
+ };
12
+ }
13
+ function serverStreamingMethod(path, init) {
14
+ return {
15
+ path,
16
+ clientStreams: false,
17
+ serverStreams: true,
18
+ ...init
19
+ };
20
+ }
21
+ function clientStreamingMethod(path, init) {
22
+ return {
23
+ path,
24
+ clientStreams: true,
25
+ serverStreams: false,
26
+ ...init
27
+ };
28
+ }
29
+ function bidiMethod(path, init) {
30
+ return {
31
+ path,
32
+ clientStreams: true,
33
+ serverStreams: true,
34
+ ...init
35
+ };
36
+ }
37
+ function isUnary(desc) {
38
+ return !desc.clientStreams && !desc.serverStreams;
39
+ }
40
+ //#endregion
41
+ //#region src/server.ts
42
+ const EMPTY = /* @__PURE__ */ new Uint8Array(0);
43
+ function detailsOf(err) {
44
+ if (!(err instanceof StatusError)) return void 0;
45
+ const d = err.details;
46
+ if (!Array.isArray(d) || d.length === 0) return void 0;
47
+ const out = [];
48
+ for (const e of d) {
49
+ if (e === null || typeof e !== "object") continue;
50
+ const a = e;
51
+ if (typeof a.typeUrl === "string" && a.value instanceof Uint8Array) out.push({
52
+ typeUrl: a.typeUrl,
53
+ value: a.value
54
+ });
55
+ }
56
+ return out.length > 0 ? out : void 0;
57
+ }
58
+ const errText = (e) => e instanceof Error ? e.message : String(e);
59
+ function windowOf(f) {
60
+ const w = f.window;
61
+ return typeof w === "number" && Number.isFinite(w) && w > 0 ? Math.floor(w) : 0;
62
+ }
63
+ var PeerState = class {
64
+ peer;
65
+ epoch;
66
+ reliable;
67
+ createdAt;
68
+ maxTombs;
69
+ maxTombBytes;
70
+ hwm = 0;
71
+ cps = [];
72
+ dead = false;
73
+ tombs = /* @__PURE__ */ new Map();
74
+ tombOrder = [];
75
+ tombBytes = 0;
76
+ tombFloor = 0;
77
+ calls = /* @__PURE__ */ new Map();
78
+ liveCalls = 0;
79
+ lastRx;
80
+ lastTx;
81
+ lastPing = 0;
82
+ constructor(peer, epoch, reliable, createdAt, maxTombs, maxTombBytes) {
83
+ this.peer = peer;
84
+ this.epoch = epoch;
85
+ this.reliable = reliable;
86
+ this.createdAt = createdAt;
87
+ this.maxTombs = maxTombs;
88
+ this.maxTombBytes = maxTombBytes;
89
+ this.lastRx = createdAt;
90
+ this.lastTx = createdAt;
91
+ }
92
+ hwmAged(now, ttlMs) {
93
+ if (this.reliable) return this.hwm;
94
+ let aged = 0;
95
+ for (const cp of this.cps) if (now - cp.at >= ttlMs) aged = cp.hwm;
96
+ return aged;
97
+ }
98
+ addTomb(sid, term, expireAt) {
99
+ if (sid <= this.tombFloor) return;
100
+ const size = term?.payload?.length ?? 0;
101
+ const old = this.tombs.get(sid);
102
+ if (old !== void 0) {
103
+ this.tombBytes += size - old.size;
104
+ old.term = term;
105
+ old.size = size;
106
+ if (expireAt > old.expireAt) old.expireAt = expireAt;
107
+ return;
108
+ }
109
+ this.tombs.set(sid, {
110
+ sid,
111
+ term,
112
+ size,
113
+ expireAt,
114
+ lastReplay: 0
115
+ });
116
+ this.tombOrder.push(sid);
117
+ this.tombBytes += size;
118
+ for (let i = 0; this.tombBytes > this.maxTombBytes && i < this.tombOrder.length; i++) {
119
+ const tb = this.tombs.get(this.tombOrder[i]);
120
+ if (tb !== void 0 && tb.term !== void 0) {
121
+ this.tombBytes -= tb.size;
122
+ tb.term = void 0;
123
+ tb.size = 0;
124
+ }
125
+ }
126
+ while (this.tombs.size > this.maxTombs) {
127
+ let lowest = 0;
128
+ for (const tsid of this.tombs.keys()) if (lowest === 0 || tsid < lowest) lowest = tsid;
129
+ const tb = this.tombs.get(lowest);
130
+ if (tb !== void 0) this.tombBytes -= tb.size;
131
+ this.tombs.delete(lowest);
132
+ if (this.tombFloor < lowest) this.tombFloor = lowest;
133
+ }
134
+ }
135
+ removeTomb(sid) {
136
+ const tb = this.tombs.get(sid);
137
+ if (tb !== void 0) {
138
+ this.tombBytes -= tb.size;
139
+ this.tombs.delete(sid);
140
+ }
141
+ }
142
+ replayDue(tb, now, rtiMs) {
143
+ return tb.term !== void 0 && now - tb.lastReplay >= rtiMs;
144
+ }
145
+ replayTomb(tb, now, rtiMs) {
146
+ if (tb.term === void 0 || now - tb.lastReplay < rtiMs) return void 0;
147
+ tb.lastReplay = now;
148
+ this.lastTx = now;
149
+ return tb.term;
150
+ }
151
+ };
152
+ var PeerSlot = class {
153
+ peer;
154
+ epochs = /* @__PURE__ */ new Map();
155
+ liveCalls = 0;
156
+ replyBudget;
157
+ resetAt = /* @__PURE__ */ new Map();
158
+ pendingResets = /* @__PURE__ */ new Map();
159
+ constructor(peer) {
160
+ this.peer = peer;
161
+ }
162
+ };
163
+ const eksid = (epoch, sid) => `${epoch}:${sid}`;
164
+ var Server = class {
165
+ epoch;
166
+ tx;
167
+ mode;
168
+ maxHandlerTimeoutMs;
169
+ rxCfg;
170
+ methodRx;
171
+ limits;
172
+ codecs;
173
+ compressors;
174
+ maxRecv;
175
+ maxSend;
176
+ stallMs;
177
+ services = /* @__PURE__ */ new Map();
178
+ serving = false;
179
+ slots = /* @__PURE__ */ new Map();
180
+ pendingResetTotal = 0;
181
+ resetAtTotal = 0;
182
+ replyBudgetTotal = 0;
183
+ drain = false;
184
+ closed = false;
185
+ liveTasks = 0;
186
+ idleWaiters = [];
187
+ sawUnreliable = false;
188
+ sw = new Sweeper();
189
+ constructor(tx, opts = {}) {
190
+ this.epoch = nonzeroEpoch();
191
+ this.tx = tx;
192
+ this.mode = {
193
+ reliable: opts.reliable ?? (hasTransportInfo(tx) ? tx.reliable() : false),
194
+ timing: resolveTiming(opts.timing)
195
+ };
196
+ this.maxHandlerTimeoutMs = opts.maxHandlerTimeoutMs ?? 0;
197
+ this.rxCfg = resolveRxConfig(opts.rxBuffer);
198
+ this.methodRx = new Map(Object.entries(opts.methodRxBuffer ?? {}).map(([k, v]) => [k, resolveRxConfig(v)]));
199
+ this.limits = resolveLimits(opts.limits);
200
+ this.codecs = new Map(Object.entries(opts.codecs ?? {}));
201
+ this.compressors = new Map(Object.entries(opts.compressors ?? {}));
202
+ this.maxRecv = sizeOr(opts.maxRecvMsgSize, DEFAULT_MAX_RECV_MSG_SIZE);
203
+ this.maxSend = sizeOr(opts.maxSendMsgSize, DEFAULT_MAX_SEND_MSG_SIZE);
204
+ this.stallMs = opts.timing?.stallMs ?? 3e4;
205
+ }
206
+ register(desc, handler) {
207
+ if (this.serving) throw new Error("drpc: register called after the server started serving");
208
+ this.services.set(desc.path, {
209
+ desc,
210
+ handler
211
+ });
212
+ }
213
+ rxReliable(ctx) {
214
+ return ctx.reliable ?? this.mode.reliable;
215
+ }
216
+ async handle(f, ctx = {}) {
217
+ this.serving = true;
218
+ if (isReset(f)) {
219
+ if (f.epoch !== this.epoch) return;
220
+ this.resetByPeerSid(ctx.peer, f.sid, f.peerEpoch);
221
+ return;
222
+ }
223
+ const peer = ctx.peer;
224
+ const sid = f.sid;
225
+ const slot = this.slots.get(peer);
226
+ const ps = slot?.epochs.get(f.epoch);
227
+ const now = nowMs();
228
+ if (isPing(f)) {
229
+ if (ps !== void 0) ps.lastRx = now;
230
+ if (sid === 0) return;
231
+ if (ps?.calls.has(sid)) return;
232
+ const tb = ps?.tombs.get(sid);
233
+ if (ps !== void 0 && tb !== void 0) {
234
+ const rti = this.mode.timing.retransmitMs;
235
+ if (ps.replayDue(tb, now, rti) && this.allowReply(slot, now)) {
236
+ const replay = ps.replayTomb(tb, now, rti);
237
+ if (replay !== void 0) {
238
+ await this.send(replay, ctx);
239
+ return;
240
+ }
241
+ }
242
+ if (tb.term !== void 0) return;
243
+ return this.sendReset(slot, f, ctx);
244
+ }
245
+ return this.sendReset(slot, f, ctx);
246
+ }
247
+ const st = ps?.calls.get(sid);
248
+ if (st !== void 0) {
249
+ await st.handleRx(f, ctx);
250
+ return;
251
+ }
252
+ if (shapeOf(f) === 16) return;
253
+ if (ps !== void 0) {
254
+ const tb = ps.tombs.get(sid);
255
+ if (tb !== void 0) {
256
+ ps.lastRx = now;
257
+ const rti = this.mode.timing.retransmitMs;
258
+ if (ps.replayDue(tb, now, rti) && this.allowReply(slot, now)) {
259
+ const replay = ps.replayTomb(tb, now, rti);
260
+ if (replay !== void 0) await this.send(replay, ctx);
261
+ }
262
+ return;
263
+ }
264
+ if (sid <= ps.tombFloor) {
265
+ ps.lastRx = now;
266
+ return;
267
+ }
268
+ }
269
+ if (isOpen(f) && f.seq === 1) {
270
+ if (ps !== void 0) {
271
+ if (sid <= ps.hwmAged(now, this.mode.timing.tombstoneMs)) return this.sendReset(slot, f, ctx);
272
+ }
273
+ return this.open(ctx, f);
274
+ }
275
+ if (this.rxReliable(ctx)) return this.sendReset(slot, f, ctx);
276
+ const s = this.ensureSlot(peer);
277
+ const k = eksid(f.epoch, sid);
278
+ if (!s.pendingResets.has(k) && this.pendingResetTotal < this.limits.maxPendingResets) {
279
+ s.pendingResets.set(k, {
280
+ due: now + this.mode.timing.holdMs,
281
+ echo: f.epoch,
282
+ peerEcho: f.peerEpoch,
283
+ epoch: f.epoch,
284
+ sid
285
+ });
286
+ this.pendingResetTotal++;
287
+ this.sawUnreliable = true;
288
+ }
289
+ this.kickSweep();
290
+ }
291
+ async open(ctx, f) {
292
+ if (this.drain || this.closed) return this.sendReset(this.slots.get(ctx.peer), f, ctx);
293
+ const rel = this.rxReliable(ctx);
294
+ const reg = this.services.get(f.method);
295
+ if (reg === void 0) return this.rejectOpen(ctx, f, 12, "method not found");
296
+ let codec;
297
+ if (f.codec === "") codec = {
298
+ request: reg.desc.request,
299
+ response: reg.desc.response
300
+ };
301
+ else {
302
+ const named = this.codecs.get(f.codec);
303
+ if (named === void 0) return this.rejectOpen(ctx, f, 12, `unsupported codec: ${f.codec}`);
304
+ codec = named.resolve(reg.desc);
305
+ }
306
+ let comp;
307
+ if (f.compressor) {
308
+ comp = this.compressors.get(f.compressor);
309
+ if (comp === void 0) return this.rejectOpen(ctx, f, 12, `unsupported compressor: ${f.compressor}`);
310
+ }
311
+ const now = nowMs();
312
+ const slot = this.ensureSlot(ctx.peer);
313
+ const ps = this.ensurePeer(slot, f.epoch, now, rel);
314
+ if (slot.liveCalls >= this.limits.maxLiveCalls) return this.rejectOpen(ctx, f, 8, "too many concurrent calls");
315
+ const cfg = this.methodRx.get(reg.desc.path) ?? this.rxCfg;
316
+ const rxCfg = {
317
+ size: reliableRxSize(cfg.size, rel),
318
+ policy: cfg.policy
319
+ };
320
+ const st = new ServerStream(this, ctx.peer, f.epoch, f.sid, reg, codec, rxCfg, rel, comp, this.maxRecv, this.maxSend, this.stallMs);
321
+ st.ps = ps;
322
+ st.slot = slot;
323
+ if (rel) st.flowTx.observe(windowOf(f));
324
+ if (f.timeoutMs !== void 0) {
325
+ let d = f.timeoutMs;
326
+ if (this.maxHandlerTimeoutMs > 0 && d > this.maxHandlerTimeoutMs) d = this.maxHandlerTimeoutMs;
327
+ st.deadlineAt = now + d;
328
+ if (d <= 0) st.cancel(statusError(4, "call timeout"));
329
+ else {
330
+ const t = setTimeout(() => st.cancel(statusError(4, "call timeout")), d);
331
+ unrefTimer(t);
332
+ st.deadlineTimer = t;
333
+ }
334
+ }
335
+ st.metadata = f.header;
336
+ ps.calls.set(f.sid, st);
337
+ slot.liveCalls++;
338
+ ps.liveCalls++;
339
+ if (ps.hwm < f.sid) ps.hwm = f.sid;
340
+ ps.dead = false;
341
+ ps.lastRx = now;
342
+ if (slot.pendingResets.delete(eksid(f.epoch, f.sid))) this.pendingResetTotal--;
343
+ this.liveTasks++;
344
+ this.kickSweep();
345
+ const desc = reg.desc;
346
+ if (isUnary(desc)) this.runUnary(st, f);
347
+ else if (!desc.clientStreams) {
348
+ if (f.payload !== void 0) st.rxq.tryPut(f);
349
+ if (isClose(f)) st.rxEOF.trip();
350
+ st.sendH();
351
+ this.runStream(st);
352
+ } else {
353
+ if (f.payload !== void 0 || isClose(f)) st.rxDropped++;
354
+ st.sendH();
355
+ this.runStream(st);
356
+ }
357
+ }
358
+ async rejectOpen(ctx, f, code, msg) {
359
+ const t = frame({
360
+ epoch: this.epoch,
361
+ sid: f.sid,
362
+ seq: 1,
363
+ flags: 2,
364
+ desc: msg,
365
+ peerEpoch: f.epoch
366
+ });
367
+ t.code = code;
368
+ if (!this.rxReliable(ctx)) {
369
+ const now = nowMs();
370
+ const slot = this.ensureSlot(ctx.peer);
371
+ const ps = this.ensurePeer(slot, f.epoch, now, false);
372
+ ps.lastRx = now;
373
+ if (ps.hwm < f.sid) ps.hwm = f.sid;
374
+ ps.addTomb(f.sid, t, now + this.mode.timing.tombstoneMs);
375
+ this.kickSweep();
376
+ }
377
+ await this.send(t, ctx);
378
+ }
379
+ async runUnary(st, open) {
380
+ let term;
381
+ try {
382
+ let err;
383
+ let resp;
384
+ try {
385
+ const req = await st.decodeMessage(open);
386
+ resp = await st.reg.handler(req, st.context);
387
+ } catch (e) {
388
+ err = toStatusError(e);
389
+ }
390
+ if (err === void 0 && st.signal.aborted) err = abortCause(st.signal);
391
+ if (err === void 0) try {
392
+ st.setResponse(resp);
393
+ } catch (e) {
394
+ err = statusError(13, `marshal response: ${errText(e)}`);
395
+ }
396
+ const t = await st.terminalFrame(err);
397
+ if (st.suppressTerm) return;
398
+ term = t;
399
+ st.transmitTerminal(t).catch(noop);
400
+ } finally {
401
+ this.finish(st, term);
402
+ this.taskDone();
403
+ }
404
+ }
405
+ async runStream(st) {
406
+ let term;
407
+ try {
408
+ const desc = st.reg.desc;
409
+ let err;
410
+ try {
411
+ if (!desc.clientStreams) {
412
+ const req = await st.recv();
413
+ if (req === void 0) throw statusError(2, "missing request message");
414
+ await st.reg.handler(req, st, st.context);
415
+ } else if (!desc.serverStreams) {
416
+ const resp = await st.reg.handler(st, st.context);
417
+ st.setResponse(resp);
418
+ } else await st.reg.handler(st, st.context);
419
+ } catch (e) {
420
+ err = toStatusError(e);
421
+ }
422
+ if (err === void 0 && st.signal.aborted) err = abortCause(st.signal);
423
+ const t = await st.terminalFrame(err);
424
+ if (st.suppressTerm) return;
425
+ term = t;
426
+ st.transmitTerminal(t).catch(noop);
427
+ } finally {
428
+ this.finish(st, term);
429
+ this.taskDone();
430
+ }
431
+ }
432
+ finish(st, term) {
433
+ const now = nowMs();
434
+ const slot = st.slot;
435
+ const ps = st.ps;
436
+ if (ps !== void 0) {
437
+ ps.calls.delete(st.sid);
438
+ ps.liveCalls--;
439
+ if (!st.reliable) {
440
+ let ttl = this.mode.timing.tombstoneMs;
441
+ if (st.deadlineAt !== void 0) ttl = Math.max(ttl, st.deadlineAt - now);
442
+ if (ps.dead) term = void 0;
443
+ ps.addTomb(st.sid, term, now + ttl);
444
+ }
445
+ }
446
+ if (slot !== void 0 && slot.liveCalls > 0) slot.liveCalls--;
447
+ st.flowTx.release();
448
+ st.cancel(statusError(1, "call finished"));
449
+ if (st.deadlineTimer !== void 0) {
450
+ clearTimeout(st.deadlineTimer);
451
+ st.deadlineTimer = void 0;
452
+ }
453
+ this.kickSweep();
454
+ }
455
+ resetByPeerSid(peer, sid, peerEpoch) {
456
+ const slot = this.slots.get(peer);
457
+ if (slot === void 0) return;
458
+ const targets = [];
459
+ for (const [epoch, ps] of slot.epochs) {
460
+ if (peerEpoch !== 0 && epoch !== peerEpoch) continue;
461
+ const st = ps.calls.get(sid);
462
+ if (st !== void 0) targets.push(st);
463
+ }
464
+ const cause = statusError(14, "call reset by peer");
465
+ for (const st of targets) {
466
+ st.suppressTerm = true;
467
+ st.cancel(cause);
468
+ }
469
+ }
470
+ async sendReset(slot, f, ctx) {
471
+ if (!this.rxReliable(ctx)) {
472
+ const n = nowMs();
473
+ const s = slot ?? this.ensureSlot(ctx.peer);
474
+ const k = eksid(f.epoch, f.sid);
475
+ const last = s.resetAt.get(k);
476
+ if (last !== void 0) {
477
+ if (n - last < this.mode.timing.retransmitMs) return;
478
+ } else if (this.resetAtTotal >= this.limits.maxPendingResets) return;
479
+ if (!this.allowReply(s, n)) return;
480
+ if (!s.resetAt.has(k)) this.resetAtTotal++;
481
+ s.resetAt.set(k, n);
482
+ this.sawUnreliable = true;
483
+ this.kickSweep();
484
+ }
485
+ await this.send(resetFor(f), ctx);
486
+ }
487
+ allowReply(slot, now) {
488
+ let b = slot.replyBudget;
489
+ if (b === void 0) {
490
+ if (this.replyBudgetTotal >= this.limits.maxPendingResets) return false;
491
+ b = {
492
+ windowStart: now,
493
+ n: 0
494
+ };
495
+ slot.replyBudget = b;
496
+ this.replyBudgetTotal++;
497
+ }
498
+ if (now - b.windowStart >= this.mode.timing.retransmitMs) {
499
+ b.windowStart = now;
500
+ b.n = 0;
501
+ }
502
+ if (b.n >= this.limits.maxRepliesPerRTI) return false;
503
+ b.n++;
504
+ return true;
505
+ }
506
+ async gracefulStop() {
507
+ this.drain = true;
508
+ await this.waitIdle();
509
+ this.closed = true;
510
+ this.sw.stop();
511
+ }
512
+ async stop() {
513
+ this.drain = true;
514
+ this.closed = true;
515
+ const targets = [];
516
+ for (const slot of this.slots.values()) for (const ps of slot.epochs.values()) for (const st of ps.calls.values()) targets.push(st);
517
+ const cause = statusError(14, "server stopped");
518
+ for (const st of targets) st.cancel(cause);
519
+ await this.waitIdle();
520
+ this.sw.stop();
521
+ }
522
+ disconnectPeer(peer, err) {
523
+ const slot = this.slots.get(peer);
524
+ if (slot === void 0) return;
525
+ const targets = [];
526
+ for (const ps of slot.epochs.values()) for (const st of ps.calls.values()) targets.push(st);
527
+ this.pendingResetTotal -= slot.pendingResets.size;
528
+ this.resetAtTotal -= slot.resetAt.size;
529
+ if (slot.replyBudget !== void 0) this.replyBudgetTotal--;
530
+ this.slots.delete(peer);
531
+ const cause = err === void 0 || err === null ? statusError(14, "transport closed") : statusError(14, `transport closed: ${err instanceof Error ? err.message : String(err)}`);
532
+ for (const st of targets) st.cancel(cause);
533
+ }
534
+ waitIdle() {
535
+ if (this.liveTasks === 0) return Promise.resolve();
536
+ return new Promise((res) => this.idleWaiters.push(res));
537
+ }
538
+ taskDone() {
539
+ this.liveTasks--;
540
+ if (this.liveTasks === 0) {
541
+ const ws = this.idleWaiters.splice(0);
542
+ for (const w of ws) w();
543
+ }
544
+ }
545
+ /** @internal */
546
+ get timing() {
547
+ return this.mode.timing;
548
+ }
549
+ /** @internal */
550
+ send(f, ctx) {
551
+ return Promise.resolve(this.tx.handle(f, ctx)).catch(noop);
552
+ }
553
+ /** @internal */
554
+ sendOrThrow(f, ctx) {
555
+ return Promise.resolve(this.tx.handle(f, ctx));
556
+ }
557
+ /** @internal */
558
+ txFor(peer) {
559
+ return peer === void 0 ? {} : { peer };
560
+ }
561
+ /** @internal */
562
+ allowReplyFor(peer, now) {
563
+ const slot = this.slots.get(peer);
564
+ if (slot === void 0) return false;
565
+ return this.allowReply(slot, now);
566
+ }
567
+ ensureSlot(peer) {
568
+ let slot = this.slots.get(peer);
569
+ if (slot === void 0) {
570
+ slot = new PeerSlot(peer);
571
+ this.slots.set(peer, slot);
572
+ }
573
+ return slot;
574
+ }
575
+ ensurePeer(slot, epoch, now, reliable) {
576
+ let ps = slot.epochs.get(epoch);
577
+ if (ps !== void 0) return ps;
578
+ const dead = [];
579
+ for (const p of slot.epochs.values()) if (p.liveCalls === 0) dead.push(p);
580
+ if (dead.length >= this.limits.maxDeadPeers) {
581
+ let oldest = dead[0];
582
+ for (const p of dead) if (p.createdAt < oldest.createdAt) oldest = p;
583
+ slot.epochs.delete(oldest.epoch);
584
+ }
585
+ ps = new PeerState(slot.peer, epoch, reliable, now, this.limits.maxTombstones, this.limits.maxTombstoneBytes);
586
+ slot.epochs.set(epoch, ps);
587
+ if (!reliable) this.sawUnreliable = true;
588
+ return ps;
589
+ }
590
+ /** @internal */
591
+ kickSweep() {
592
+ if (!this.sawUnreliable) return;
593
+ this.sw.kick(this.mode.timing.tickMs, () => this.sweep(nowMs()), () => this.hasWork());
594
+ }
595
+ hasWork() {
596
+ if (this.pendingResetTotal > 0 || this.resetAtTotal > 0 || this.replyBudgetTotal > 0) return true;
597
+ for (const slot of this.slots.values()) for (const ps of slot.epochs.values()) if (!ps.reliable) return true;
598
+ return false;
599
+ }
600
+ sweep(now) {
601
+ const t = this.mode.timing;
602
+ const jobs = [];
603
+ const lost = [];
604
+ for (const [peerKey, slot] of this.slots) {
605
+ for (const [k, pr] of slot.pendingResets) {
606
+ if (now < pr.due) continue;
607
+ const ps = slot.epochs.get(pr.epoch);
608
+ if (ps !== void 0 && (ps.calls.has(pr.sid) || ps.tombs.has(pr.sid))) {
609
+ slot.pendingResets.delete(k);
610
+ this.pendingResetTotal--;
611
+ continue;
612
+ }
613
+ if (!this.allowReply(slot, now)) continue;
614
+ slot.pendingResets.delete(k);
615
+ this.pendingResetTotal--;
616
+ jobs.push({
617
+ f: frame({
618
+ flags: 4,
619
+ epoch: pr.echo,
620
+ peerEpoch: pr.peerEcho,
621
+ sid: pr.sid
622
+ }),
623
+ ctx: this.txFor(slot.peer)
624
+ });
625
+ }
626
+ for (const [k, at] of slot.resetAt) if (now - at > t.tombstoneMs) {
627
+ slot.resetAt.delete(k);
628
+ this.resetAtTotal--;
629
+ }
630
+ if (slot.replyBudget !== void 0 && now - slot.replyBudget.windowStart > t.tombstoneMs) {
631
+ slot.replyBudget = void 0;
632
+ this.replyBudgetTotal--;
633
+ }
634
+ for (const [epoch, ps] of slot.epochs) {
635
+ if (ps.reliable) continue;
636
+ ps.cps.push({
637
+ at: now,
638
+ hwm: ps.hwm
639
+ });
640
+ while (ps.cps.length > 1 && now - ps.cps[1].at >= t.tombstoneMs) ps.cps.shift();
641
+ const aged = ps.hwmAged(now, t.tombstoneMs);
642
+ for (const [sid, tb] of ps.tombs) if (now > tb.expireAt && sid <= aged) ps.removeTomb(sid);
643
+ if (ps.tombOrder.length > 2 * ps.tombs.size + 16) ps.tombOrder = ps.tombOrder.filter((sid) => ps.tombs.has(sid));
644
+ if (ps.liveCalls > 0 && !ps.dead) {
645
+ if (now - ps.lastRx >= t.livenessMs) {
646
+ ps.dead = true;
647
+ for (const st of ps.calls.values()) {
648
+ st.suppressTerm = true;
649
+ lost.push(st);
650
+ }
651
+ for (const tb of ps.tombs.values()) {
652
+ ps.tombBytes -= tb.size;
653
+ tb.term = void 0;
654
+ tb.size = 0;
655
+ }
656
+ } else if (now - ps.lastTx >= t.probeMs && now - ps.lastPing >= t.probeMs) {
657
+ ps.lastPing = now;
658
+ ps.lastTx = now;
659
+ jobs.push({
660
+ f: frame({
661
+ epoch: this.epoch,
662
+ flags: 8,
663
+ peerEpoch: epoch
664
+ }),
665
+ ctx: this.txFor(slot.peer)
666
+ });
667
+ }
668
+ }
669
+ if (ps.liveCalls === 0 && ps.tombs.size === 0 && now - ps.lastRx > 2 * t.tombstoneMs) slot.epochs.delete(epoch);
670
+ }
671
+ for (const ps of slot.epochs.values()) {
672
+ if (ps.reliable) continue;
673
+ for (const st of ps.calls.values()) {
674
+ const f = st.probeDue(now, t.probeMs, this.epoch);
675
+ if (f !== void 0) {
676
+ ps.lastTx = now;
677
+ jobs.push({
678
+ f,
679
+ ctx: this.txFor(slot.peer)
680
+ });
681
+ }
682
+ }
683
+ }
684
+ if (slot.epochs.size === 0 && slot.liveCalls === 0 && slot.pendingResets.size === 0 && slot.resetAt.size === 0 && slot.replyBudget === void 0) this.slots.delete(peerKey);
685
+ }
686
+ const cause = statusError(14, "peer lost");
687
+ for (const st of lost) st.cancel(cause);
688
+ for (const j of jobs) this.send(j.f, j.ctx);
689
+ }
690
+ };
691
+ var ServerStream = class {
692
+ server;
693
+ peer;
694
+ clientEpoch;
695
+ sid;
696
+ reg;
697
+ reliable;
698
+ comp;
699
+ maxRecv;
700
+ maxSend;
701
+ stallMs;
702
+ context;
703
+ /** @internal */ ps;
704
+ /** @internal */ slot;
705
+ /** @internal */ deadlineAt;
706
+ /** @internal */ deadlineTimer;
707
+ /** @internal */ metadata;
708
+ /** @internal */ suppressTerm = false;
709
+ /** @internal */ flowTx = new FlowSender();
710
+ flowRx = new FlowReceiver();
711
+ txSeq = new TxSeq();
712
+ /** @internal */ txHeader;
713
+ hdrSent = false;
714
+ hdrFlushed = false;
715
+ hdrFrame;
716
+ /** @internal */ trailerMd;
717
+ resp;
718
+ respSet = false;
719
+ lastRx;
720
+ lastTx;
721
+ lastProbe = 0;
722
+ hReplayAt = 0;
723
+ rxWin = new RxWindow();
724
+ /** @internal */ rxq;
725
+ rxCfg;
726
+ /** @internal */ rxDropped = 0;
727
+ /** @internal */ rxEOF = new Latch();
728
+ ctrl = new AbortController();
729
+ endLatch = new Latch();
730
+ /** @internal */
731
+ constructor(server, peer, clientEpoch, sid, reg, codec, rxCfg, reliable, comp, maxRecv, maxSend, stallMs) {
732
+ this.server = server;
733
+ this.peer = peer;
734
+ this.clientEpoch = clientEpoch;
735
+ this.sid = sid;
736
+ this.reg = reg;
737
+ this.reliable = reliable;
738
+ this.comp = comp;
739
+ this.maxRecv = maxRecv;
740
+ this.maxSend = maxSend;
741
+ this.stallMs = stallMs;
742
+ this.reqCodec = codec.request;
743
+ this.resCodec = codec.response;
744
+ this.rxCfg = rxCfg;
745
+ this.rxq = new FrameQueue(rxCfg.size);
746
+ this.rxWin.l = 1;
747
+ this.rxWin.strict = reliable;
748
+ if (reliable) this.flowRx.enable(rxCfg.size);
749
+ const n = nowMs();
750
+ this.lastRx = n;
751
+ this.lastTx = n;
752
+ const self = this;
753
+ this.context = {
754
+ signal: this.ctrl.signal,
755
+ get metadata() {
756
+ return self.metadata;
757
+ },
758
+ peer,
759
+ method: reg.desc.path,
760
+ get deadline() {
761
+ return self.deadlineAt;
762
+ },
763
+ setHeader: (md) => this.setHeader(md),
764
+ sendHeader: (md) => this.sendHeader(md),
765
+ setTrailer: (md) => {
766
+ if (Object.keys(md).length === 0) return;
767
+ try {
768
+ validateMetadata(md);
769
+ } catch {
770
+ return;
771
+ }
772
+ this.trailerMd = metadataJoin(this.trailerMd, md);
773
+ }
774
+ };
775
+ }
776
+ /** @internal */ reqCodec;
777
+ /** @internal */ resCodec;
778
+ get signal() {
779
+ return this.ctrl.signal;
780
+ }
781
+ /** @internal */
782
+ cancel(cause) {
783
+ this.ctrl.abort(cause);
784
+ this.endLatch.trip();
785
+ }
786
+ /** @internal */
787
+ async handleRx(f, ctx) {
788
+ if (hasUnknownFlags(f) || !legalShape(shapeOf(f))) {
789
+ this.cancel(statusError(13, `drpc: frame carries unsupported flags 0x${f.flags.toString(16)}`));
790
+ return;
791
+ }
792
+ if (shapeOf(f) === 16) {
793
+ if (this.reliable) this.flowTx.grant(windowOf(f));
794
+ return;
795
+ }
796
+ if (isOpen(f)) {
797
+ if (f.seq !== 1) {
798
+ this.rxDropped++;
799
+ return;
800
+ }
801
+ if (this.reliable) {
802
+ this.cancel(statusError(13, "reliable transport lost or reordered a frame"));
803
+ return;
804
+ }
805
+ this.noteValidatedRx();
806
+ if (!isUnary(this.reg.desc) || this.hdrFrame !== void 0) this.replayH();
807
+ return;
808
+ }
809
+ switch (this.rxWin.check(f.seq)) {
810
+ case 1:
811
+ this.noteValidatedRx();
812
+ return;
813
+ case 2: return;
814
+ case 3:
815
+ this.cancel(statusError(15, "seq window overrun: >W_fwd consecutive frames lost"));
816
+ return;
817
+ case 4:
818
+ this.cancel(statusError(13, "reliable transport lost or reordered a frame"));
819
+ return;
820
+ case 0: break;
821
+ }
822
+ this.noteValidatedRx();
823
+ if (isTerminal(f)) this.cancel(frameStatus(f));
824
+ else if (isHalfClose(f)) this.rxEOF.trip();
825
+ else if (isData(f)) {
826
+ if (isUnary(this.reg.desc) || !this.reg.desc.clientStreams) {
827
+ this.rxDropped++;
828
+ return;
829
+ }
830
+ if (this.reliable) {
831
+ if (this.flowRx.active) {
832
+ if (!this.rxq.tryPut(f)) this.cancel(statusError(13, "drpc: peer exceeded the advertised flow-control window"));
833
+ } else if (!await this.rxq.putBlocking(f, this.endLatch, ctx.signal)) {
834
+ this.rxDropped++;
835
+ this.cancel(statusError(14, "transport closed during delivery"));
836
+ }
837
+ } else this.rxq.putDrop(f, this.rxCfg.policy);
838
+ } else this.rxDropped++;
839
+ }
840
+ noteValidatedRx() {
841
+ const n = nowMs();
842
+ this.lastRx = n;
843
+ if (this.ps !== void 0) this.ps.lastRx = n;
844
+ }
845
+ async recv() {
846
+ for (;;) {
847
+ const f = this.rxq.tryTake();
848
+ if (f !== void 0) {
849
+ const msg = await this.decodeMessage(f);
850
+ this.grantWindow(1);
851
+ return msg;
852
+ }
853
+ if (this.rxEOF.tripped) return void 0;
854
+ if (this.ctrl.signal.aborted) throw abortCause(this.ctrl.signal);
855
+ await Promise.race([
856
+ this.rxq.readable(),
857
+ this.rxEOF.wait(),
858
+ this.endLatch.wait()
859
+ ]);
860
+ }
861
+ }
862
+ async *[Symbol.asyncIterator]() {
863
+ for (;;) {
864
+ const m = await this.recv();
865
+ if (m === void 0) return;
866
+ yield m;
867
+ }
868
+ }
869
+ /** @internal */
870
+ transmit(f) {
871
+ const n = nowMs();
872
+ this.lastTx = n;
873
+ if (this.ps !== void 0) this.ps.lastTx = n;
874
+ return this.server.send(f, this.server.txFor(this.peer));
875
+ }
876
+ nextFrame() {
877
+ const f = frame({
878
+ epoch: this.server.epoch,
879
+ sid: this.sid,
880
+ seq: this.txSeq.next()
881
+ });
882
+ f.peerEpoch = this.clientEpoch;
883
+ return f;
884
+ }
885
+ attachHeader(f, force) {
886
+ if (this.hdrSent) return;
887
+ if (this.txHeader !== void 0) {
888
+ f.header = this.txHeader;
889
+ this.hdrSent = true;
890
+ return;
891
+ }
892
+ if (force) {
893
+ f.header = {};
894
+ this.hdrSent = true;
895
+ }
896
+ }
897
+ /** @internal */
898
+ sendH() {
899
+ const f = this.nextFrame();
900
+ if (this.reliable) f.window = this.rxCfg.size;
901
+ this.attachHeader(f, false);
902
+ if (this.hdrFrame === void 0) this.hdrFrame = f;
903
+ this.transmit(f).catch(noop);
904
+ }
905
+ grantWindow(n) {
906
+ if (this.ctrl.signal.aborted) return;
907
+ const g = this.flowRx.consumed(n);
908
+ if (g === 0) return;
909
+ const f = frame({
910
+ epoch: this.server.epoch,
911
+ sid: this.sid,
912
+ peerEpoch: this.clientEpoch,
913
+ flags: 16,
914
+ window: g
915
+ });
916
+ this.transmit(f).catch(noop);
917
+ }
918
+ replayH() {
919
+ const n = nowMs();
920
+ if (n - this.hReplayAt < this.server.timing.retransmitMs) return;
921
+ this.hReplayAt = n;
922
+ if (!this.server.allowReplyFor(this.peer, n)) return;
923
+ let f = this.hdrFrame;
924
+ if (f === void 0) {
925
+ f = this.nextFrame();
926
+ if (this.reliable) f.window = this.rxCfg.size;
927
+ this.attachHeader(f, false);
928
+ }
929
+ this.transmit(f).catch(noop);
930
+ }
931
+ /** @internal */
932
+ probeDue(now, probeMs, epoch) {
933
+ if (now - this.lastRx < probeMs || now - this.lastTx < probeMs || now - this.lastProbe < probeMs) return;
934
+ this.lastProbe = now;
935
+ return frame({
936
+ epoch,
937
+ sid: this.sid,
938
+ flags: 8,
939
+ peerEpoch: this.clientEpoch
940
+ });
941
+ }
942
+ /** @internal */
943
+ setHeader(md) {
944
+ if (Object.keys(md).length === 0) return;
945
+ validateMetadata(md);
946
+ if (this.hdrFlushed || this.hdrSent) throw illegalHeaderWrite();
947
+ this.txHeader = metadataJoin(this.txHeader, md);
948
+ }
949
+ /** @internal */
950
+ async sendHeader(md) {
951
+ if (md !== void 0) validateMetadata(md);
952
+ if (this.hdrFlushed || this.hdrSent) throw illegalHeaderWrite();
953
+ this.hdrFlushed = true;
954
+ if (md !== void 0) this.txHeader = metadataJoin(this.txHeader, md);
955
+ const f = this.nextFrame();
956
+ this.attachHeader(f, true);
957
+ if (this.hdrFrame === void 0) this.hdrFrame = f;
958
+ try {
959
+ await this.transmitOrThrow(f);
960
+ } catch (e) {
961
+ this.undoRefused(f, e);
962
+ throw toStatusError(e);
963
+ }
964
+ }
965
+ async send(msg) {
966
+ const raw = this.resCodec.marshal(msg);
967
+ if (!this.reg.desc.serverStreams) throw statusError(13, "send on a non-server-streaming call");
968
+ if (!this.flowTx.tryAcquire()) switch (await this.flowTx.acquire(this.endLatch, this.stallMs)) {
969
+ case "ok": break;
970
+ case "stalled": throw statusError(14, `drpc: flow-control stall: the peer granted no credit for ${this.stallMs}ms`);
971
+ default: throw abortCause(this.ctrl.signal);
972
+ }
973
+ let enc;
974
+ try {
975
+ enc = await this.encodePayload(raw);
976
+ } catch (e) {
977
+ this.flowTx.undo();
978
+ throw e;
979
+ }
980
+ if (this.ctrl.signal.aborted) throw abortCause(this.ctrl.signal);
981
+ const f = this.nextFrame();
982
+ f.payload = enc.bytes;
983
+ if (enc.compressed) f.flags |= 32;
984
+ this.attachHeader(f, false);
985
+ try {
986
+ await this.transmitOrThrow(f);
987
+ } catch (e) {
988
+ this.undoRefused(f, e);
989
+ throw e;
990
+ }
991
+ }
992
+ transmitOrThrow(f) {
993
+ const n = nowMs();
994
+ this.lastTx = n;
995
+ if (this.ps !== void 0) this.ps.lastTx = n;
996
+ return this.server.sendOrThrow(f, this.server.txFor(this.peer));
997
+ }
998
+ undoRefused(f, err) {
999
+ if (!isMessageTooLarge(err)) return;
1000
+ this.txSeq.undo(f.seq);
1001
+ if (isData(f)) this.flowTx.undo();
1002
+ }
1003
+ async encodePayload(payload) {
1004
+ const enc = this.comp === void 0 ? rawPayload(payload) : await compressPayload(this.comp, payload);
1005
+ checkSendSize(enc.bytes.length, this.maxSend);
1006
+ return enc;
1007
+ }
1008
+ /** @internal */
1009
+ async decodeMessage(f) {
1010
+ const raw = f.payload ?? EMPTY;
1011
+ const payload = isCompressed(f) ? await decompressPayload(this.comp, raw, this.maxRecv) : raw;
1012
+ checkRecvSize(payload.length, this.maxRecv);
1013
+ return this.reqCodec.unmarshal(payload);
1014
+ }
1015
+ /** @internal */
1016
+ setResponse(resp) {
1017
+ if (this.respSet) throw statusError(13, "response already set");
1018
+ this.resp = this.resCodec.marshal(resp);
1019
+ this.respSet = true;
1020
+ }
1021
+ /** @internal */
1022
+ async terminalFrame(err) {
1023
+ let enc;
1024
+ let st = err;
1025
+ if (st === void 0 && this.respSet) try {
1026
+ enc = await this.encodePayload(this.resp ?? EMPTY);
1027
+ } catch (e) {
1028
+ st = toStatusError(e);
1029
+ }
1030
+ const f = this.nextFrame();
1031
+ f.flags = 2;
1032
+ if (this.txHeader !== void 0) {
1033
+ f.header = this.txHeader;
1034
+ this.hdrSent = true;
1035
+ }
1036
+ if (this.trailerMd !== void 0) f.trailer = this.trailerMd;
1037
+ if (st !== void 0) {
1038
+ setFrameError(f, st);
1039
+ f.details = detailsOf(st);
1040
+ return f;
1041
+ }
1042
+ if (enc !== void 0) {
1043
+ f.payload = enc.bytes;
1044
+ if (enc.compressed) f.flags |= 32;
1045
+ }
1046
+ f.code = 0;
1047
+ return f;
1048
+ }
1049
+ /** @internal */
1050
+ async transmitTerminal(f) {
1051
+ let err = await this.tryTransmit(f);
1052
+ if (err === void 0) return;
1053
+ if (f.details !== void 0 && f.details.length > 0) {
1054
+ f.details = void 0;
1055
+ const e2 = await this.tryTransmit(f);
1056
+ if (e2 === void 0) return;
1057
+ err = e2;
1058
+ }
1059
+ if (f.payload === void 0 && f.code === 8) return;
1060
+ f.payload = void 0;
1061
+ f.flags &= -33;
1062
+ f.details = void 0;
1063
+ setFrameError(f, statusError(8, `drpc: the terminal frame does not fit the transport: ${errText(err)}`));
1064
+ await this.tryTransmit(f);
1065
+ }
1066
+ async tryTransmit(f) {
1067
+ try {
1068
+ await this.transmitOrThrow(f);
1069
+ return;
1070
+ } catch (e) {
1071
+ return isMessageTooLarge(e) ? e : void 0;
1072
+ }
1073
+ }
1074
+ };
1075
+ function illegalHeaderWrite() {
1076
+ return statusError(13, "drpc: sendHeader called multiple times");
1077
+ }
1078
+ //#endregion
1079
+ export { ClientStream, Code, Conn, DEFAULT_MAX_RECV_MSG_SIZE, DEFAULT_MAX_SEND_MSG_SIZE, DEFAULT_STALL_MS, DropPolicy, EndOfStreamError, FlagClose, FlagCompressed, FlagOpen, FlagPing, FlagReset, FlagWindow, K_LOUD, MessageTooLargeError, RxVerdict, RxWindow, SHAPE_MASK, Server, StatusError, TxSeq, W_FWD, W_INIT, abortCause, bidiMethod, clientStreamingMethod, cloneMetadata, decodeBase64, decodeEnvelop, decodeFrame, encodeBase64, encodeEnvelop, encodeFrame, frame, frameStatus, getCompressor, hasConnAttacher, hasTransportInfo, hasUnknownFlags, isBinaryKey, isClose, isCompressed, isData, isHalfClose, isHeaderFrame, isMessageTooLarge, isOpen, isPing, isReset, isTerminal, isUnary, isWindow, legalShape, metadataJoin, resetFor, serverStreamingMethod, setFrameError, shapeOf, statusDetails, statusError, toStatusError, unaryMethod, unpack, validateMetadata, validateMetadataPair };