@optimystic/db-p2p 0.22.0 → 0.24.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/src/cluster/client.d.ts +10 -0
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +30 -1
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +39 -14
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +226 -119
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/service.d.ts +32 -1
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +43 -2
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cohort-topic/host.js +34 -11
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +37 -7
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +77 -19
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +9 -3
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +88 -2
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +134 -28
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +25 -1
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/logger.d.ts +17 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +19 -2
- package/dist/src/logger.js.map +1 -1
- package/dist/src/matchmaking/query-transport.js +3 -3
- package/dist/src/matchmaking/query-transport.js.map +1 -1
- package/dist/src/owned-block-seed.d.ts +6 -3
- package/dist/src/owned-block-seed.d.ts.map +1 -1
- package/dist/src/owned-block-seed.js +16 -3
- package/dist/src/owned-block-seed.js.map +1 -1
- package/dist/src/peer-address-book.d.ts +72 -0
- package/dist/src/peer-address-book.d.ts.map +1 -0
- package/dist/src/peer-address-book.js +123 -0
- package/dist/src/peer-address-book.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts +4 -4
- package/dist/src/reactivity/notify-transport.js +6 -6
- package/dist/src/reactivity/notify-transport.js.map +1 -1
- package/dist/src/reactivity/push-state-gossip.js +2 -2
- package/dist/src/reactivity/push-state-gossip.js.map +1 -1
- package/dist/src/reactivity/recover-transport.d.ts +6 -2
- package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
- package/dist/src/reactivity/recover-transport.js +7 -3
- package/dist/src/reactivity/recover-transport.js.map +1 -1
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +11 -2
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +30 -0
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +95 -3
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +62 -9
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +242 -73
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/rn.d.ts +3 -0
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +3 -0
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/cached-raw-storage.d.ts +83 -0
- package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
- package/dist/src/storage/cached-raw-storage.js +152 -0
- package/dist/src/storage/cached-raw-storage.js.map +1 -0
- package/dist/src/storage/cached-store-driver.d.ts +186 -0
- package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
- package/dist/src/storage/cached-store-driver.js +775 -0
- package/dist/src/storage/cached-store-driver.js.map +1 -0
- package/dist/src/storage/i-raw-storage.d.ts +12 -5
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.d.ts +234 -0
- package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
- package/dist/src/storage/shared-cache-pool.js +354 -0
- package/dist/src/storage/shared-cache-pool.js.map +1 -0
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts +13 -6
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/cohort-topic-mesh-harness.js +15 -6
- package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
- package/dist/src/testing/raw-storage-conformance.js +35 -2
- package/dist/src/testing/raw-storage-conformance.js.map +1 -1
- package/package.json +3 -3
- package/readme.md +668 -668
- package/src/cluster/block-transfer.ts +424 -424
- package/src/cluster/client.ts +119 -88
- package/src/cluster/cluster-error.ts +64 -64
- package/src/cluster/cluster-policy.ts +203 -203
- package/src/cluster/cluster-repo.ts +242 -122
- package/src/cluster/cluster-size-coupling.ts +45 -45
- package/src/cluster/commit-cert.ts +139 -139
- package/src/cluster/i-transaction-state-store.ts +43 -43
- package/src/cluster/memory-transaction-state-store.ts +56 -56
- package/src/cluster/peer-key-binding.ts +37 -37
- package/src/cluster/persistent-transaction-state-store.ts +92 -92
- package/src/cluster/quorum-restore.ts +223 -223
- package/src/cluster/reconcile-block.ts +203 -203
- package/src/cluster/service.ts +293 -241
- package/src/cluster/supermajority-coupling.ts +37 -37
- package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
- package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
- package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
- package/src/cohort-topic/change-bridge.ts +109 -109
- package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
- package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
- package/src/cohort-topic/fret-trust-anchor.ts +153 -153
- package/src/cohort-topic/host.ts +42 -11
- package/src/cohort-topic/index.ts +13 -13
- package/src/cohort-topic/membership-publish-sink.ts +20 -20
- package/src/cohort-topic/membership-source.ts +68 -68
- package/src/cohort-topic/peer-codec.ts +31 -31
- package/src/cohort-topic/peer-sig.ts +86 -86
- package/src/cohort-topic/protocols.ts +71 -71
- package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
- package/src/cohort-topic/size-estimator.ts +16 -16
- package/src/cohort-topic/stream-util.ts +79 -19
- package/src/cohort-topic/threshold-crypto.ts +239 -239
- package/src/cohort-topic/topic-router.ts +77 -77
- package/src/dispute/arbitrator-selection.ts +138 -138
- package/src/dispute/cascade.ts +524 -524
- package/src/dispute/dispute-service.ts +11 -5
- package/src/dispute/invalidation.ts +625 -625
- package/src/inbound-authorization.ts +190 -190
- package/src/index.ts +52 -49
- package/src/libp2p-key-network.ts +1120 -990
- package/src/libp2p-node-base.ts +1675 -1651
- package/src/libp2p-node-rn.ts +30 -30
- package/src/libp2p-node.ts +36 -36
- package/src/logger.ts +19 -2
- package/src/matchmaking/aggregate-counts.ts +104 -104
- package/src/matchmaking/index.ts +20 -20
- package/src/matchmaking/module.ts +363 -363
- package/src/matchmaking/protocols.ts +51 -51
- package/src/matchmaking/provider-manager.ts +95 -95
- package/src/matchmaking/query-handler.ts +88 -88
- package/src/matchmaking/query-transport.ts +3 -3
- package/src/matchmaking/seeker-manager.ts +64 -64
- package/src/matchmaking/seeker-walk-client.ts +293 -293
- package/src/matchmaking/traffic-validation.ts +195 -195
- package/src/optimystic-node.ts +36 -36
- package/src/owned-block-seed.ts +53 -40
- package/src/peer-address-book.ts +149 -0
- package/src/protocol-limits.ts +33 -33
- package/src/reactivity/forwarder-host.ts +438 -438
- package/src/reactivity/index.ts +19 -19
- package/src/reactivity/notify-transport.ts +144 -144
- package/src/reactivity/origination-manager.ts +192 -192
- package/src/reactivity/protocols.ts +61 -61
- package/src/reactivity/push-state-gossip.ts +291 -291
- package/src/reactivity/recover-transport.ts +7 -3
- package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
- package/src/reactivity/subscriber-registry.ts +96 -96
- package/src/reactivity/subscription-manager.ts +450 -450
- package/src/reactivity/topic-bytes.ts +37 -37
- package/src/repo/client.ts +12 -2
- package/src/repo/cluster-coordinator.ts +99 -3
- package/src/repo/coordinator-repo.ts +281 -74
- package/src/repo/types.ts +7 -7
- package/src/rn.ts +39 -36
- package/src/rpc-deadline.ts +45 -45
- package/src/storage/arachnode-partition.ts +74 -74
- package/src/storage/cached-raw-storage.ts +180 -0
- package/src/storage/cached-store-driver.ts +859 -0
- package/src/storage/i-kv-store.ts +8 -8
- package/src/storage/i-raw-storage.ts +12 -5
- package/src/storage/kv-raw-storage.ts +135 -135
- package/src/storage/memory-kv-store.ts +28 -28
- package/src/storage/memory-storage.ts +25 -25
- package/src/storage/memory-store-driver.ts +157 -157
- package/src/storage/raw-store-codec.ts +42 -42
- package/src/storage/raw-store-driver.ts +80 -80
- package/src/storage/ring-selector.ts +317 -317
- package/src/storage/ring-shift-coordinator.ts +271 -271
- package/src/storage/shared-cache-pool.ts +452 -0
- package/src/storage/storage-repo.ts +1014 -1014
- package/src/testing/cohort-topic-mesh-harness.ts +673 -663
- package/src/testing/index.ts +8 -8
- package/src/testing/matchmaking-mesh-harness.ts +475 -475
- package/src/testing/raw-storage-conformance.ts +453 -417
- package/src/testing/reactivity-mesh-harness.ts +922 -922
- package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
- package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
- package/dist/src/storage/restoration-coordinator-v2.js +0 -172
- package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
package/src/cluster/client.ts
CHANGED
|
@@ -1,88 +1,119 @@
|
|
|
1
|
-
import type { PeerId, IPeerNetwork, ICluster, ClusterRecord } from '@optimystic/db-core';
|
|
2
|
-
import { blockIdToBytes, blockIdsForTransforms } from '@optimystic/db-core';
|
|
3
|
-
import { ProtocolClient } from '../protocol-client.js';
|
|
4
|
-
import { peerIdFromString } from '@libp2p/peer-id';
|
|
5
|
-
import { isClusterErrorEnvelope, clusterErrorFromEnvelope } from './cluster-error.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
1
|
+
import type { PeerId, IPeerNetwork, ICluster, ClusterRecord } from '@optimystic/db-core';
|
|
2
|
+
import { blockIdToBytes, blockIdsForTransforms } from '@optimystic/db-core';
|
|
3
|
+
import { ProtocolClient } from '../protocol-client.js';
|
|
4
|
+
import { peerIdFromString } from '@libp2p/peer-id';
|
|
5
|
+
import { isClusterErrorEnvelope, clusterErrorFromEnvelope } from './cluster-error.js';
|
|
6
|
+
import type { RedirectPayload } from '../repo/redirect.js';
|
|
7
|
+
import { mergeRecordPeerAddresses } from '../peer-address-book.js';
|
|
8
|
+
import { createLogger } from '../logger.js';
|
|
9
|
+
import { withRpcDeadlineDefaults, type RpcDeadlineOptions } from '../rpc-deadline.js';
|
|
10
|
+
import { MAX_CONTROL_MESSAGE_BYTES } from '../protocol-limits.js';
|
|
11
|
+
|
|
12
|
+
const log = createLogger('cluster-client');
|
|
13
|
+
|
|
14
|
+
export class ClusterClient extends ProtocolClient implements ICluster {
|
|
15
|
+
private constructor(peerId: PeerId, peerNetwork: IPeerNetwork, readonly protocolPrefix?: string) {
|
|
16
|
+
super(peerId, peerNetwork);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Create a new client instance */
|
|
20
|
+
public static create(peerId: PeerId, peerNetwork: IPeerNetwork, protocolPrefix?: string): ClusterClient {
|
|
21
|
+
return new ClusterClient(peerId, peerNetwork, protocolPrefix);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async update(record: ClusterRecord, hop: number = 0, options?: RpcDeadlineOptions): Promise<ClusterRecord> {
|
|
25
|
+
const message = {
|
|
26
|
+
operation: 'update',
|
|
27
|
+
record
|
|
28
|
+
};
|
|
29
|
+
// Every node registers the cluster service under its network-prefixed
|
|
30
|
+
// protocol id (`/optimystic/<network>/cluster/1.0.0`); the bare
|
|
31
|
+
// `/db-p2p/cluster/1.0.0` is never registered, so there is no legacy
|
|
32
|
+
// fallback to attempt — a single dial whose error propagates directly.
|
|
33
|
+
const protocol = (this.protocolPrefix ?? '/db-p2p') + '/cluster/1.0.0';
|
|
34
|
+
// Apply the client-level dial/response deadline so a silent cluster peer
|
|
35
|
+
// can't hang the coordinator forever; an explicit caller override wins.
|
|
36
|
+
// Response is a ClusterRecord (peer set + signatures + small metadata) → control cap.
|
|
37
|
+
const response = await this.processMessage<unknown>(message, protocol, { ...withRpcDeadlineDefaults(options), maxDataLength: MAX_CONTROL_MESSAGE_BYTES });
|
|
38
|
+
|
|
39
|
+
// A member that threw inside `update` replies with a structured error
|
|
40
|
+
// envelope rather than aborting the stream; rethrow the server's real
|
|
41
|
+
// error (preserving name/code) so the coordinator sees the true cause.
|
|
42
|
+
if (isClusterErrorEnvelope(response)) {
|
|
43
|
+
throw clusterErrorFromEnvelope(response);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Type the redirect branch against the payload the service actually produces
|
|
47
|
+
// (`RedirectPayload`), not a hand-written narrower shape — the old `{ id: string }`
|
|
48
|
+
// literal is what structurally discarded the `addrs` the sender took the trouble to embed.
|
|
49
|
+
const redirectResponse = response as Partial<RedirectPayload>;
|
|
50
|
+
if (redirectResponse?.redirect?.peers?.length) {
|
|
51
|
+
if (hop >= 2) {
|
|
52
|
+
throw new Error('Redirect loop detected in ClusterClient (max hops reached)')
|
|
53
|
+
}
|
|
54
|
+
const currentIdStr = this.peerId.toString()
|
|
55
|
+
const next = redirectResponse.redirect.peers.find((p) => p.id !== currentIdStr) ?? redirectResponse.redirect.peers[0]!
|
|
56
|
+
const nextId = peerIdFromString(next.id)
|
|
57
|
+
if (next.id === currentIdStr) {
|
|
58
|
+
throw new Error('Redirect loop detected in ClusterClient (same peer)')
|
|
59
|
+
}
|
|
60
|
+
// Learn the redirect target's addresses BEFORE dialing it: the redirect is the only
|
|
61
|
+
// notice we get that this peer matters, and if it is relay-only we have no address
|
|
62
|
+
// for it at all. Merging after the dial would help only some later hop.
|
|
63
|
+
this.peerNetwork.recordPeerAddresses?.(nextId, next.addrs ?? [])
|
|
64
|
+
await this.recordCoordinatorForRecordIfSupported(record, nextId)
|
|
65
|
+
const nextClient = ClusterClient.create(nextId, this.peerNetwork, this.protocolPrefix)
|
|
66
|
+
// Thread the caller's *original* options through the redirect hop (the
|
|
67
|
+
// recursive call re-applies its own defaults) so the deadline survives a redirect.
|
|
68
|
+
return await nextClient.update(record, hop + 1, options)
|
|
69
|
+
}
|
|
70
|
+
const updated = response as ClusterRecord;
|
|
71
|
+
// The member's reply carries the cohort's peer map too, and it may name addresses this
|
|
72
|
+
// node lacks — the coordinator's own half of the same exchange the service handles inbound.
|
|
73
|
+
this.recordRecordPeerAddresses(updated)
|
|
74
|
+
return updated;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Offer every address a cluster record carries for its cohort to the dialer's address book.
|
|
79
|
+
*
|
|
80
|
+
* Shares its traversal — and its cap on how many peers one record may introduce — with the
|
|
81
|
+
* inbound side (`ClusterService.learnPeerAddresses`); validation, the per-peer cap, and the
|
|
82
|
+
* trust boundary live in the implementation behind `recordPeerAddresses`. No self-filter is
|
|
83
|
+
* passed: `this.peerId` is the peer we DIALED, not this node, and only the sink knows the
|
|
84
|
+
* local node's identity.
|
|
85
|
+
*/
|
|
86
|
+
private recordRecordPeerAddresses(record: ClusterRecord): void {
|
|
87
|
+
const sink = this.peerNetwork.recordPeerAddresses
|
|
88
|
+
if (!sink) return
|
|
89
|
+
mergeRecordPeerAddresses(record?.peers, (pid, addrs) => sink.call(this.peerNetwork, pid, addrs), log)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Record a coordinator-affinity hint for the block this record's op is
|
|
94
|
+
* coordinated on, so a follow-up op can dial `peerId` directly.
|
|
95
|
+
*
|
|
96
|
+
* The op lives at `record.message.operations[0]` — `record.message` is a
|
|
97
|
+
* RepoMessage `{ operations: [...] }`, NOT a bare `{ commit }`/`{ pend }`.
|
|
98
|
+
* Reading `record.message.commit`/`.pend` (the old code) always saw
|
|
99
|
+
* `undefined`, so this hint was dead code and never recorded anything.
|
|
100
|
+
*
|
|
101
|
+
* The key is `blockIdToBytes(<the coordinated block id>)` — the sha256 digest
|
|
102
|
+
* findCoordinator/recordCoordinator key the cache on — NOT raw utf8 of the id
|
|
103
|
+
* (which would never be retrieved). Commit anchors on blockIds[0] (where
|
|
104
|
+
* CoordinatorRepo runs consensus + verifyResponsibility), not tailId. Pend
|
|
105
|
+
* anchors on a real block id (blockIdsForTransforms), not a structural
|
|
106
|
+
* transforms field name. ClusterClient only ever carries commit/pend.
|
|
107
|
+
*/
|
|
108
|
+
private async recordCoordinatorForRecordIfSupported(record: ClusterRecord, peerId: PeerId): Promise<void> {
|
|
109
|
+
const op = record.message.operations[0]
|
|
110
|
+
if (!op) return
|
|
111
|
+
let id: string | undefined
|
|
112
|
+
if ('commit' in op) id = op.commit.blockIds[0]
|
|
113
|
+
else if ('pend' in op) id = blockIdsForTransforms(op.pend.transforms)[0]
|
|
114
|
+
if (id == null) return
|
|
115
|
+
const kbytes = await blockIdToBytes(id)
|
|
116
|
+
const pn: any = this.peerNetwork as any
|
|
117
|
+
if (typeof pn?.recordCoordinator === 'function') pn.recordCoordinator(kbytes, peerId)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structured error envelope for the cluster protocol.
|
|
3
|
-
*
|
|
4
|
-
* When a cluster member throws while processing an `update` (validation,
|
|
5
|
-
* signature, merge, or consensus failure), the service serializes the error as
|
|
6
|
-
* this envelope and closes the stream normally instead of aborting it. The
|
|
7
|
-
* coordinator's {@link ClusterClient} detects the envelope and rethrows a real
|
|
8
|
-
* `Error` carrying the server's message/name/code — so the genuine cause is
|
|
9
|
-
* visible on the coordinator side (which already enables `optimystic:*` debug)
|
|
10
|
-
* rather than collapsing into an opaque `StreamResetError`.
|
|
11
|
-
*
|
|
12
|
-
* The marker key `__clusterError` cannot collide with a successful response: a
|
|
13
|
-
* {@link ClusterRecord} never carries a top-level field by this name, and the
|
|
14
|
-
* redirect payload is keyed under `redirect`.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/** Top-level key that marks a response as a structured cluster error. */
|
|
18
|
-
export const CLUSTER_ERROR_KEY = '__clusterError';
|
|
19
|
-
|
|
20
|
-
/** Serializable error detail carried in a {@link ClusterErrorEnvelope}. */
|
|
21
|
-
export interface ClusterErrorDetail {
|
|
22
|
-
message: string;
|
|
23
|
-
name: string;
|
|
24
|
-
/** Stable error code (e.g. libp2p `ERR_*`) when the source error carries one. */
|
|
25
|
-
code?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Wire shape of a structured cluster error response. */
|
|
29
|
-
export interface ClusterErrorEnvelope {
|
|
30
|
-
__clusterError: ClusterErrorDetail;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Build a {@link ClusterErrorEnvelope} from any thrown value. */
|
|
34
|
-
export function toClusterErrorEnvelope(err: unknown): ClusterErrorEnvelope {
|
|
35
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
36
|
-
const rawCode = (error as { code?: unknown }).code;
|
|
37
|
-
const detail: ClusterErrorDetail = {
|
|
38
|
-
message: error.message,
|
|
39
|
-
name: error.name
|
|
40
|
-
};
|
|
41
|
-
if (typeof rawCode === 'string' && rawCode.length > 0) {
|
|
42
|
-
detail.code = rawCode;
|
|
43
|
-
}
|
|
44
|
-
return { __clusterError: detail };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Type guard: is `value` a structured cluster error envelope (not a record/redirect)? */
|
|
48
|
-
export function isClusterErrorEnvelope(value: unknown): value is ClusterErrorEnvelope {
|
|
49
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
50
|
-
const detail = (value as { [CLUSTER_ERROR_KEY]?: unknown })[CLUSTER_ERROR_KEY];
|
|
51
|
-
return typeof detail === 'object' && detail !== null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Reconstruct a real `Error` from a cluster error envelope, preserving `name`
|
|
56
|
-
* and `code` so the coordinator's reputation/penalty classification still works.
|
|
57
|
-
*/
|
|
58
|
-
export function clusterErrorFromEnvelope(envelope: ClusterErrorEnvelope): Error {
|
|
59
|
-
const detail = envelope[CLUSTER_ERROR_KEY];
|
|
60
|
-
const error = new Error(detail.message || 'cluster update failed');
|
|
61
|
-
if (detail.name) error.name = detail.name;
|
|
62
|
-
if (detail.code) (error as { code?: string }).code = detail.code;
|
|
63
|
-
return error;
|
|
64
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Structured error envelope for the cluster protocol.
|
|
3
|
+
*
|
|
4
|
+
* When a cluster member throws while processing an `update` (validation,
|
|
5
|
+
* signature, merge, or consensus failure), the service serializes the error as
|
|
6
|
+
* this envelope and closes the stream normally instead of aborting it. The
|
|
7
|
+
* coordinator's {@link ClusterClient} detects the envelope and rethrows a real
|
|
8
|
+
* `Error` carrying the server's message/name/code — so the genuine cause is
|
|
9
|
+
* visible on the coordinator side (which already enables `optimystic:*` debug)
|
|
10
|
+
* rather than collapsing into an opaque `StreamResetError`.
|
|
11
|
+
*
|
|
12
|
+
* The marker key `__clusterError` cannot collide with a successful response: a
|
|
13
|
+
* {@link ClusterRecord} never carries a top-level field by this name, and the
|
|
14
|
+
* redirect payload is keyed under `redirect`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Top-level key that marks a response as a structured cluster error. */
|
|
18
|
+
export const CLUSTER_ERROR_KEY = '__clusterError';
|
|
19
|
+
|
|
20
|
+
/** Serializable error detail carried in a {@link ClusterErrorEnvelope}. */
|
|
21
|
+
export interface ClusterErrorDetail {
|
|
22
|
+
message: string;
|
|
23
|
+
name: string;
|
|
24
|
+
/** Stable error code (e.g. libp2p `ERR_*`) when the source error carries one. */
|
|
25
|
+
code?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Wire shape of a structured cluster error response. */
|
|
29
|
+
export interface ClusterErrorEnvelope {
|
|
30
|
+
__clusterError: ClusterErrorDetail;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Build a {@link ClusterErrorEnvelope} from any thrown value. */
|
|
34
|
+
export function toClusterErrorEnvelope(err: unknown): ClusterErrorEnvelope {
|
|
35
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
36
|
+
const rawCode = (error as { code?: unknown }).code;
|
|
37
|
+
const detail: ClusterErrorDetail = {
|
|
38
|
+
message: error.message,
|
|
39
|
+
name: error.name
|
|
40
|
+
};
|
|
41
|
+
if (typeof rawCode === 'string' && rawCode.length > 0) {
|
|
42
|
+
detail.code = rawCode;
|
|
43
|
+
}
|
|
44
|
+
return { __clusterError: detail };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Type guard: is `value` a structured cluster error envelope (not a record/redirect)? */
|
|
48
|
+
export function isClusterErrorEnvelope(value: unknown): value is ClusterErrorEnvelope {
|
|
49
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
50
|
+
const detail = (value as { [CLUSTER_ERROR_KEY]?: unknown })[CLUSTER_ERROR_KEY];
|
|
51
|
+
return typeof detail === 'object' && detail !== null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Reconstruct a real `Error` from a cluster error envelope, preserving `name`
|
|
56
|
+
* and `code` so the coordinator's reputation/penalty classification still works.
|
|
57
|
+
*/
|
|
58
|
+
export function clusterErrorFromEnvelope(envelope: ClusterErrorEnvelope): Error {
|
|
59
|
+
const detail = envelope[CLUSTER_ERROR_KEY];
|
|
60
|
+
const error = new Error(detail.message || 'cluster update failed');
|
|
61
|
+
if (detail.name) error.name = detail.name;
|
|
62
|
+
if (detail.code) (error as { code?: string }).code = detail.code;
|
|
63
|
+
return error;
|
|
64
|
+
}
|