@peerbit/stream 5.1.3 → 5.1.5
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/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +51 -56
- package/dist/src/index.js.map +1 -1
- package/dist/src/rust-core.d.ts +13 -3
- package/dist/src/rust-core.d.ts.map +1 -1
- package/dist/src/rust-core.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +57 -56
- package/src/rust-core.ts +16 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/stream",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.5",
|
|
4
4
|
"description": "A building block for direct streaming protocols",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -96,11 +96,11 @@
|
|
|
96
96
|
"uint8arraylist": "^2.4.8",
|
|
97
97
|
"uint8arrays": "^5.1.0",
|
|
98
98
|
"yallist": "^5.0.0",
|
|
99
|
+
"@peerbit/cache": "3.1.1",
|
|
99
100
|
"@peerbit/crypto": "3.1.4",
|
|
100
|
-
"@peerbit/
|
|
101
|
-
"@peerbit/stream-interface": "6.0.13",
|
|
101
|
+
"@peerbit/stream-interface": "6.0.14",
|
|
102
102
|
"@peerbit/time": "3.0.1",
|
|
103
|
-
"@peerbit/
|
|
103
|
+
"@peerbit/logger": "2.0.1"
|
|
104
104
|
},
|
|
105
105
|
"scripts": {
|
|
106
106
|
"bench": "TS_NODE_PROJECT=../../../tsconfig.json node --loader ts-node/esm ./benchmark/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -54,7 +54,11 @@ import {
|
|
|
54
54
|
getDeliveryHopTrace,
|
|
55
55
|
getResponsePriorityFromMessage,
|
|
56
56
|
hasDeliveryHop,
|
|
57
|
+
isAcknowledgeAnyWhereDeliveryMode,
|
|
58
|
+
isAcknowledgeDeliveryMode,
|
|
57
59
|
isAcknowledgedDeliveryMode,
|
|
60
|
+
isAnyWhereDeliveryMode,
|
|
61
|
+
isSilentDeliveryMode,
|
|
58
62
|
setDeliveryOriginHop,
|
|
59
63
|
} from "@peerbit/stream-interface";
|
|
60
64
|
import type {
|
|
@@ -126,6 +130,7 @@ export {
|
|
|
126
130
|
type RustDecodedFanoutUnicast,
|
|
127
131
|
type RustDecodedPubSubMessage,
|
|
128
132
|
type RustEagerBlockCache,
|
|
133
|
+
type RustEagerBlockCacheStats,
|
|
129
134
|
type RustFanoutTree,
|
|
130
135
|
type RustLanesInit,
|
|
131
136
|
type RustParentUpgradeGateOptions,
|
|
@@ -163,6 +168,17 @@ const markPromiseHandled = <T>(promise: Promise<T>): Promise<T> => {
|
|
|
163
168
|
return promise;
|
|
164
169
|
};
|
|
165
170
|
|
|
171
|
+
const closeRawStreamBestEffort = (stream: Stream): void => {
|
|
172
|
+
try {
|
|
173
|
+
// WebRTC close can reject later while waiting for FIN_ACK. These lifecycle
|
|
174
|
+
// paths are intentionally fire-and-forget, so observe the rejection without
|
|
175
|
+
// delaying pruning or shutdown.
|
|
176
|
+
void Promise.resolve(stream.close?.()).catch(() => {});
|
|
177
|
+
} catch {
|
|
178
|
+
// Closing discarded streams is best-effort, including synchronous failures.
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
166
182
|
const logError = (e?: any) => {
|
|
167
183
|
if (e?.message === "Cannot push value onto an ended pushable") {
|
|
168
184
|
return; // ignore since we are trying to push to a closed stream
|
|
@@ -478,7 +494,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
478
494
|
await waitForDrain(raw, this.outboundAbortController.signal);
|
|
479
495
|
}
|
|
480
496
|
}
|
|
481
|
-
raw
|
|
497
|
+
closeRawStreamBestEffort(raw);
|
|
482
498
|
} catch (err) {
|
|
483
499
|
candidate.aborted = true;
|
|
484
500
|
try {
|
|
@@ -518,12 +534,13 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
518
534
|
if (this.outboundStreams.length <= 1) return;
|
|
519
535
|
if (reset && this._outboundPruneTimer) {
|
|
520
536
|
clearTimeout(this._outboundPruneTimer);
|
|
537
|
+
this._outboundPruneTimer = undefined;
|
|
521
538
|
}
|
|
522
539
|
if (!this._outboundPruneTimer) {
|
|
523
|
-
this._outboundPruneTimer = setTimeout(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
);
|
|
540
|
+
this._outboundPruneTimer = setTimeout(() => {
|
|
541
|
+
this._outboundPruneTimer = undefined;
|
|
542
|
+
this.pruneOutboundCandidates();
|
|
543
|
+
}, PeerStreams.OUTBOUND_GRACE_MS);
|
|
527
544
|
}
|
|
528
545
|
}
|
|
529
546
|
constructor(init: PeerStreamsInit) {
|
|
@@ -692,9 +709,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
692
709
|
try {
|
|
693
710
|
f.raw.abort?.(new AbortError("Failed write" as any));
|
|
694
711
|
} catch {}
|
|
695
|
-
|
|
696
|
-
f.raw.close?.();
|
|
697
|
-
} catch {}
|
|
712
|
+
closeRawStreamBestEffort(f.raw);
|
|
698
713
|
}
|
|
699
714
|
// If more than one remains schedule prune; else ensure outbound event raised
|
|
700
715
|
if (this.outboundStreams.length > 1) this._scheduleOutboundPrune(true);
|
|
@@ -805,11 +820,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
805
820
|
} catch {
|
|
806
821
|
logger.error("Failed to abort inbound stream");
|
|
807
822
|
}
|
|
808
|
-
|
|
809
|
-
drop.raw.close?.();
|
|
810
|
-
} catch {
|
|
811
|
-
logger.error("Failed to close inbound stream");
|
|
812
|
-
}
|
|
823
|
+
closeRawStreamBestEffort(drop.raw);
|
|
813
824
|
}
|
|
814
825
|
const abortController = new AbortController();
|
|
815
826
|
const decoded = pipe(stream, (source) =>
|
|
@@ -868,9 +879,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
868
879
|
try {
|
|
869
880
|
candidate.abortController.abort();
|
|
870
881
|
} catch {}
|
|
871
|
-
|
|
872
|
-
candidate.raw.close?.();
|
|
873
|
-
} catch {}
|
|
882
|
+
closeRawStreamBestEffort(candidate.raw);
|
|
874
883
|
}
|
|
875
884
|
this.inboundStreams = survivors;
|
|
876
885
|
// update legacy references if they were pruned
|
|
@@ -903,7 +912,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
903
912
|
try {
|
|
904
913
|
record.raw.abort?.(reason);
|
|
905
914
|
} catch {}
|
|
906
|
-
|
|
915
|
+
closeRawStreamBestEffort(record.raw);
|
|
907
916
|
}
|
|
908
917
|
if (this.rawInboundStream === record.raw) {
|
|
909
918
|
const next = this.inboundStreams[0];
|
|
@@ -923,8 +932,9 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
923
932
|
*/
|
|
924
933
|
|
|
925
934
|
async attachOutboundStream(stream: Stream) {
|
|
926
|
-
if (this.outboundStreams
|
|
935
|
+
if (this.outboundStreams.some((candidate) => candidate.raw === stream)) {
|
|
927
936
|
return; // duplicate
|
|
937
|
+
}
|
|
928
938
|
this._addOutboundCandidate(stream);
|
|
929
939
|
if (this.outboundStreams.length === 1) {
|
|
930
940
|
this.dispatchEvent(new CustomEvent("stream:outbound"));
|
|
@@ -971,11 +981,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
971
981
|
} catch {
|
|
972
982
|
logger.error("Failed to close outbound pushable");
|
|
973
983
|
}
|
|
974
|
-
|
|
975
|
-
c.raw.close?.();
|
|
976
|
-
} catch {
|
|
977
|
-
logger.error("Failed to close outbound stream");
|
|
978
|
-
}
|
|
984
|
+
closeRawStreamBestEffort(c.raw);
|
|
979
985
|
}
|
|
980
986
|
this.outboundStreams = [chosen];
|
|
981
987
|
} catch (e) {
|
|
@@ -1071,7 +1077,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
1071
1077
|
} catch {
|
|
1072
1078
|
// ignore
|
|
1073
1079
|
}
|
|
1074
|
-
|
|
1080
|
+
closeRawStreamBestEffort(inbound.raw);
|
|
1075
1081
|
} catch {
|
|
1076
1082
|
logger.error("Failed to close inbound stream");
|
|
1077
1083
|
}
|
|
@@ -2629,16 +2635,16 @@ export abstract class DirectStream<
|
|
|
2629
2635
|
|
|
2630
2636
|
let isForMe = false;
|
|
2631
2637
|
if (
|
|
2632
|
-
message.header.mode
|
|
2633
|
-
message.header.mode
|
|
2638
|
+
isAnyWhereDeliveryMode(message.header.mode) ||
|
|
2639
|
+
isAcknowledgeAnyWhereDeliveryMode(message.header.mode)
|
|
2634
2640
|
) {
|
|
2635
2641
|
isForMe = true;
|
|
2636
2642
|
} else {
|
|
2637
2643
|
const isFromSelf = this.publicKey.equals(from);
|
|
2638
2644
|
if (
|
|
2639
2645
|
!isFromSelf &&
|
|
2640
|
-
(message.header.mode
|
|
2641
|
-
message.header.mode
|
|
2646
|
+
(isSilentDeliveryMode(message.header.mode) ||
|
|
2647
|
+
isAcknowledgeDeliveryMode(message.header.mode))
|
|
2642
2648
|
) {
|
|
2643
2649
|
isForMe = message.header.mode.to.includes(this.publicKeyHash);
|
|
2644
2650
|
}
|
|
@@ -2663,8 +2669,8 @@ export abstract class DirectStream<
|
|
|
2663
2669
|
}
|
|
2664
2670
|
|
|
2665
2671
|
if (
|
|
2666
|
-
message.header.mode
|
|
2667
|
-
message.header.mode
|
|
2672
|
+
isSilentDeliveryMode(message.header.mode) ||
|
|
2673
|
+
isAcknowledgeDeliveryMode(message.header.mode)
|
|
2668
2674
|
) {
|
|
2669
2675
|
if (
|
|
2670
2676
|
message.header.mode.to &&
|
|
@@ -2679,8 +2685,8 @@ export abstract class DirectStream<
|
|
|
2679
2685
|
// Forward
|
|
2680
2686
|
const shouldForward =
|
|
2681
2687
|
seenBefore === 0 ||
|
|
2682
|
-
((message.header.mode
|
|
2683
|
-
message.header.mode
|
|
2688
|
+
((isAcknowledgeDeliveryMode(message.header.mode) ||
|
|
2689
|
+
isAcknowledgeAnyWhereDeliveryMode(message.header.mode)) &&
|
|
2684
2690
|
seenBefore < message.header.mode.redundancy);
|
|
2685
2691
|
|
|
2686
2692
|
if (shouldForward) {
|
|
@@ -2712,14 +2718,12 @@ export abstract class DirectStream<
|
|
|
2712
2718
|
message: DataMessage | Goodbye,
|
|
2713
2719
|
seenBefore: number,
|
|
2714
2720
|
) {
|
|
2715
|
-
if (
|
|
2716
|
-
message.header.mode instanceof AcknowledgeDelivery ||
|
|
2717
|
-
message.header.mode instanceof AcknowledgeAnyWhere
|
|
2718
|
-
) {
|
|
2721
|
+
if (isAcknowledgedDeliveryMode(message.header.mode)) {
|
|
2719
2722
|
const isRecipient =
|
|
2720
|
-
message.header.mode
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
+
isAcknowledgeAnyWhereDeliveryMode(message.header.mode) ||
|
|
2724
|
+
(message.header.mode as AcknowledgeDelivery).to.includes(
|
|
2725
|
+
this.publicKeyHash,
|
|
2726
|
+
);
|
|
2723
2727
|
const shouldAcknowledge = this.rustCore
|
|
2724
2728
|
? this.rustCore.decisions.shouldAcknowledge({
|
|
2725
2729
|
isRecipient,
|
|
@@ -2752,8 +2756,7 @@ export abstract class DirectStream<
|
|
|
2752
2756
|
// only include once (seenBefore=0) and only if we have not recently pruned
|
|
2753
2757
|
// a connection to any signer in the path
|
|
2754
2758
|
origin:
|
|
2755
|
-
(message.header.mode
|
|
2756
|
-
message.header.mode instanceof AcknowledgeDelivery) &&
|
|
2759
|
+
isAcknowledgedDeliveryMode(message.header.mode) &&
|
|
2757
2760
|
seenBefore === 0 &&
|
|
2758
2761
|
!signers.find((x) =>
|
|
2759
2762
|
this.prunedConnectionsCache?.has(x),
|
|
@@ -2974,8 +2977,8 @@ export abstract class DirectStream<
|
|
|
2974
2977
|
});
|
|
2975
2978
|
|
|
2976
2979
|
if (
|
|
2977
|
-
mode
|
|
2978
|
-
mode
|
|
2980
|
+
isAcknowledgeDeliveryMode(mode) ||
|
|
2981
|
+
isSilentDeliveryMode(mode)
|
|
2979
2982
|
) {
|
|
2980
2983
|
if (mode.to) {
|
|
2981
2984
|
let preLength = mode.to.length;
|
|
@@ -3168,7 +3171,7 @@ export abstract class DirectStream<
|
|
|
3168
3171
|
relayed?: boolean,
|
|
3169
3172
|
signal?: AbortSignal,
|
|
3170
3173
|
): Promise<{ promise: Promise<void>; startTimeout: () => void }> {
|
|
3171
|
-
if (message.header.mode
|
|
3174
|
+
if (isAnyWhereDeliveryMode(message.header.mode)) {
|
|
3172
3175
|
return {
|
|
3173
3176
|
promise: Promise.resolve(),
|
|
3174
3177
|
startTimeout: () => {},
|
|
@@ -3333,7 +3336,7 @@ export abstract class DirectStream<
|
|
|
3333
3336
|
) {
|
|
3334
3337
|
const shouldKeepCallbackForRouteLearning =
|
|
3335
3338
|
!relayed &&
|
|
3336
|
-
message.header.mode
|
|
3339
|
+
isAcknowledgeDeliveryMode(message.header.mode) &&
|
|
3337
3340
|
message.header.mode.redundancy > 1;
|
|
3338
3341
|
if (haveReceivers && !shouldKeepCallbackForRouteLearning) {
|
|
3339
3342
|
// If we have an explicit recipient list we can clear the ACK callback once we
|
|
@@ -3368,8 +3371,7 @@ export abstract class DirectStream<
|
|
|
3368
3371
|
// This matters because relays may modify `to`, and because "ack-anywhere" style probes intentionally
|
|
3369
3372
|
// do not provide an explicit recipient list.
|
|
3370
3373
|
if (
|
|
3371
|
-
message.header.mode
|
|
3372
|
-
message.header.mode instanceof AcknowledgeAnyWhere
|
|
3374
|
+
isAcknowledgedDeliveryMode(message.header.mode)
|
|
3373
3375
|
) {
|
|
3374
3376
|
const upstreamHash = messageFrom?.publicKey.hashcode();
|
|
3375
3377
|
|
|
@@ -3453,7 +3455,7 @@ export abstract class DirectStream<
|
|
|
3453
3455
|
(!message.header.signatures ||
|
|
3454
3456
|
message.header.signatures.publicKeys.length === 0) &&
|
|
3455
3457
|
message instanceof DataMessage &&
|
|
3456
|
-
message.header.mode
|
|
3458
|
+
!isSilentDeliveryMode(message.header.mode)
|
|
3457
3459
|
) {
|
|
3458
3460
|
throw new Error("Missing signature");
|
|
3459
3461
|
}
|
|
@@ -3464,8 +3466,7 @@ export abstract class DirectStream<
|
|
|
3464
3466
|
|
|
3465
3467
|
if (
|
|
3466
3468
|
(message instanceof DataMessage || message instanceof Goodbye) &&
|
|
3467
|
-
(message.header.mode
|
|
3468
|
-
message.header.mode instanceof AcknowledgeAnyWhere)
|
|
3469
|
+
isAcknowledgedDeliveryMode(message.header.mode)
|
|
3469
3470
|
) {
|
|
3470
3471
|
const deliveryDeferredPromise = await this.createDeliveryPromise(
|
|
3471
3472
|
from,
|
|
@@ -3491,8 +3492,8 @@ export abstract class DirectStream<
|
|
|
3491
3492
|
|
|
3492
3493
|
if (message instanceof DataMessage) {
|
|
3493
3494
|
if (
|
|
3494
|
-
(message.header.mode
|
|
3495
|
-
message.header.mode
|
|
3495
|
+
(isAcknowledgeDeliveryMode(message.header.mode) ||
|
|
3496
|
+
isSilentDeliveryMode(message.header.mode)) &&
|
|
3496
3497
|
!to
|
|
3497
3498
|
) {
|
|
3498
3499
|
if (message.header.mode.to.length === 0) {
|
|
@@ -3517,7 +3518,7 @@ export abstract class DirectStream<
|
|
|
3517
3518
|
for (const [neighbour, _distantPeers] of fanout) {
|
|
3518
3519
|
const stream = this.peers.get(neighbour);
|
|
3519
3520
|
if (!stream) continue;
|
|
3520
|
-
if (message.header.mode
|
|
3521
|
+
if (isSilentDeliveryMode(message.header.mode)) {
|
|
3521
3522
|
message.header.mode.to = [..._distantPeers.keys()];
|
|
3522
3523
|
promises.push(
|
|
3523
3524
|
this.waitForPeerWrite(
|
|
@@ -3539,7 +3540,7 @@ export abstract class DirectStream<
|
|
|
3539
3540
|
}
|
|
3540
3541
|
usedNeighbours.add(neighbour);
|
|
3541
3542
|
}
|
|
3542
|
-
if (message.header.mode
|
|
3543
|
+
if (isSilentDeliveryMode(message.header.mode)) {
|
|
3543
3544
|
message.header.mode.to = originalTo;
|
|
3544
3545
|
}
|
|
3545
3546
|
|
|
@@ -3549,7 +3550,7 @@ export abstract class DirectStream<
|
|
|
3549
3550
|
// a separate delivery mode.
|
|
3550
3551
|
if (
|
|
3551
3552
|
!isRelayed &&
|
|
3552
|
-
message.header.mode
|
|
3553
|
+
isAcknowledgeDeliveryMode(message.header.mode) &&
|
|
3553
3554
|
usedNeighbours.size < message.header.mode.redundancy
|
|
3554
3555
|
) {
|
|
3555
3556
|
if (this.rustCore) {
|
|
@@ -3599,7 +3600,7 @@ export abstract class DirectStream<
|
|
|
3599
3600
|
// - For acknowledged deliveries, fall through to flooding (route discovery / repair).
|
|
3600
3601
|
// - For silent deliveries, relays should not flood (prevents unnecessary fanout); origin may still flood.
|
|
3601
3602
|
// We still allow direct neighbour delivery to explicit recipients (if connected).
|
|
3602
|
-
if (isRelayed && message.header.mode
|
|
3603
|
+
if (isRelayed && isSilentDeliveryMode(message.header.mode)) {
|
|
3603
3604
|
const promises: Promise<any>[] = [];
|
|
3604
3605
|
const originalTo = message.header.mode.to;
|
|
3605
3606
|
const recipients = this.rustCore
|
package/src/rust-core.ts
CHANGED
|
@@ -112,15 +112,25 @@ export interface RustBlockProviderCache {
|
|
|
112
112
|
clear(): void;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
export type RustEagerBlockCacheStats = {
|
|
116
|
+
entries: number;
|
|
117
|
+
bytes: number;
|
|
118
|
+
peakEntries: number;
|
|
119
|
+
peakBytes: number;
|
|
120
|
+
evictions: number;
|
|
121
|
+
expirations: number;
|
|
122
|
+
};
|
|
123
|
+
|
|
115
124
|
/**
|
|
116
125
|
* Eager-block cache with native bookkeeping. Block bytes stay host-side;
|
|
117
126
|
* the native index decides retention/eviction.
|
|
118
127
|
*/
|
|
119
128
|
export interface RustEagerBlockCache {
|
|
120
|
-
add(cid: string, bytes: Uint8Array):
|
|
129
|
+
add(cid: string, bytes: Uint8Array): boolean;
|
|
121
130
|
get(cid: string): Uint8Array | undefined;
|
|
122
131
|
del(cid: string): void;
|
|
123
132
|
clear(): void;
|
|
133
|
+
stats(): RustEagerBlockCacheStats;
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
/**
|
|
@@ -150,7 +160,11 @@ export interface RustBlockExchange {
|
|
|
150
160
|
ttlMs: number;
|
|
151
161
|
maxProvidersPerCid: number;
|
|
152
162
|
}): RustBlockProviderCache;
|
|
153
|
-
createEagerCache(init: {
|
|
163
|
+
createEagerCache(init: {
|
|
164
|
+
maxEntries: number;
|
|
165
|
+
maxBytes: number;
|
|
166
|
+
ttlMs: number;
|
|
167
|
+
}): RustEagerBlockCache;
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
/**
|