@derec-alliance/nodejs 0.0.1 → 0.0.3

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/index.d.ts CHANGED
@@ -28,6 +28,122 @@ export interface SecretStore {
28
28
  remove(secretId: string, channelId: string, kind: 0 | 1 | 2): Promise<void>;
29
29
  }
30
30
 
31
+
32
+ /**
33
+ * A channel's lifecycle status, as the Rust variant name the core emits.
34
+ */
35
+ export type ChannelStatusName = "Pending" | "Paired" | "Unpairing";
36
+
37
+ /**
38
+ * A peer's role on a helper channel, as the Rust variant name.
39
+ */
40
+ export type SenderKindName =
41
+ | "Owner"
42
+ | "Helper"
43
+ | "ReplicaSource"
44
+ | "ReplicaDestination";
45
+
46
+ /**
47
+ * A member's role within a replica group, as the Rust variant name.
48
+ */
49
+ export type ReplicaRoleName = "Source" | "Destination";
50
+
51
+ /**
52
+ * Narrows a listing from {@link ChannelStore}.
53
+ *
54
+ * Every field is a restriction, and every field's empty value means "do not
55
+ * restrict on this" — a filter of all-empties selects everything.
56
+ * Restrictions combine with AND, and `exclude` is applied last, overriding
57
+ * `ids`.
58
+ *
59
+ * **Returning everything under the `secretId` and ignoring the filter is
60
+ * correct**, and the implementation to write unless there is a measured reason
61
+ * not to. The library re-applies the filter to whatever you return and drops
62
+ * what it excludes, so a superset is trimmed before anything acts on it.
63
+ *
64
+ * Pushing the filter into your query — a `WHERE` clause, a key-condition
65
+ * expression — is an optimization you opt into. It saves transferring rows the
66
+ * caller discards, which costs bandwidth everywhere and real money on a metered
67
+ * backing that bills by bytes read. Verify one against
68
+ * `library/tests/fixtures/channel_filter.json`.
69
+ *
70
+ * The asymmetry is what makes pushdown worth verifying: the re-check can drop
71
+ * rows but cannot recover one that was never returned, so selecting too *few*
72
+ * is undetectable at runtime — no exception, no event, just a share that was
73
+ * never published. That matters most here, because TypeScript accepts a
74
+ * function of fewer parameters where more are declared: a store written before
75
+ * this parameter existed still satisfies the interface and compiles clean under
76
+ * `--strict`, so the type system cannot see the gap either.
77
+ *
78
+ * Ids are decimal strings, like every other `u64` on this bridge.
79
+ */
80
+ export interface ChannelFilter<Role> {
81
+ /** Restrict to these ids. Empty selects every record. */
82
+ ids: string[];
83
+ /** Restrict to these statuses. Empty selects any status. */
84
+ status: ChannelStatusName[];
85
+ /** Restrict to this role. `null` selects any role. */
86
+ role: Role | null;
87
+ /** Omit these ids, applied after `ids`. Empty omits nothing. */
88
+ exclude: string[];
89
+ }
90
+
91
+ /**
92
+ * Narrows `listHelpers`. Ids are the channel's `channel_id` and the role is
93
+ * the **peer's** `peer_role`.
94
+ */
95
+ export type HelperFilter = ChannelFilter<SenderKindName>;
96
+
97
+ /**
98
+ * Narrows `listReplicas`. Ids are the member's `replica_id` and the role is
99
+ * the member's `role`.
100
+ */
101
+ export type ReplicaFilter = ChannelFilter<ReplicaRoleName>;
102
+
103
+ /**
104
+ * Whether a channel or member with these attributes survives `filter`.
105
+ *
106
+ * Every empty field means "do not restrict", `exclude` is applied after `ids`,
107
+ * and the restrictions combine with AND — the same contract the core states on
108
+ * {@link ChannelFilter}. A store whose backing cannot express the filter as a
109
+ * query can list and call this; that is correct but transfers the rows the
110
+ * filter exists to leave behind.
111
+ *
112
+ * `id` is a decimal string, as ids are everywhere on this bridge. `role` is the
113
+ * peer's `SenderKind` name for `listHelpers` and the member's `ReplicaRole`
114
+ * name for `listReplicas`.
115
+ */
116
+ export declare function channelFilterMatches(
117
+ filter: HelperFilter | ReplicaFilter | null | undefined,
118
+ id: string,
119
+ status: ChannelStatusName,
120
+ role: SenderKindName | ReplicaRoleName,
121
+ ): boolean;
122
+
123
+ /**
124
+ * The endpoints a peer-supplied message advertises, in the peer's own order.
125
+ *
126
+ * Yields `supported_transports` when it is non-empty, and otherwise the
127
+ * singular `transport_protocol` — which is how every implementation predating
128
+ * the offer list advertises, and the reason this is a function rather than a
129
+ * field read. Reading `transport_protocol` directly is a bug: its meaning
130
+ * narrowed to "one entry of a list, and possibly absent", so a peer that has
131
+ * moved past it looks unreachable to a reader that was correct before 0.0.3.
132
+ *
133
+ * Reports what was advertised, not what is acceptable — nothing here is
134
+ * validated, and the protocol still applies its own transport policy to
135
+ * whatever it records.
136
+ */
137
+ export declare function advertisedEndpoints(
138
+ message:
139
+ | Pick<ContactMessage, "transport_protocol" | "supported_transports">
140
+ | Pick<PairRequestMessage, "transport_protocol" | "supported_transports">
141
+ | Pick<PrePairRequestMessage, "transport_protocol" | "supported_transports">
142
+ | null
143
+ | undefined,
144
+ ): TransportProtocol[];
145
+
146
+
31
147
  /**
32
148
  * Channel-record persistence.
33
149
  *
@@ -48,7 +164,7 @@ export interface SecretStore {
48
164
  *
49
165
  * `listHelpers` and `listReplicas` are **not** arrays of that union — they
50
166
  * return a JSON array of the **inner** records with the tag stripped:
51
- * `[{ channel_id, transport, ... }, ...]`, `HelperChannel` for the first and
167
+ * `[{ schema_version, channel_id, transports, ... }, ...]`, `HelperChannel` for the first and
52
168
  * `ReplicaMember` for the second. Wrapping each element back in
53
169
  * `{ "Helper": ... }` will not decode.
54
170
  *
@@ -62,7 +178,7 @@ export interface SecretStore {
62
178
  *
63
179
  * Do not `JSON.parse` and re-serialise. Every id in these records is a `u64`,
64
180
  * and `JSON.parse` silently rounds anything above 2^53 — the corruption only
65
- * appears once a real id happens to be large. `bindings/web` implements this.
181
+ * appears once a real id happens to be large.
66
182
  */
67
183
  export interface ChannelStore {
68
184
  load(
@@ -77,8 +193,19 @@ export interface ChannelStore {
77
193
  bytes: Uint8Array,
78
194
  ): Promise<void>;
79
195
  remove(secretId: string, channelId: string, replicaId: string): Promise<boolean>;
80
- /** JSON array of the helper channels stored under `secretId`. */
81
- listHelpers(secretId: string): Promise<Uint8Array | null | undefined>;
196
+ /**
197
+ * JSON array of the helper channels stored under `secretId` that `filter`
198
+ * selects.
199
+ *
200
+ * Apply the filter in your query rather than listing everything and
201
+ * discarding rows; see {@link ChannelFilter}. The library re-applies it to
202
+ * whatever you return, so ignoring it is slow rather than wrong — but
203
+ * returning fewer rows than it selects is wrong, and undetectable.
204
+ */
205
+ listHelpers(
206
+ secretId: string,
207
+ filter: HelperFilter,
208
+ ): Promise<Uint8Array | null | undefined>;
82
209
  /**
83
210
  * JSON array of the replica-group members stored under `secretId`,
84
211
  * including this device's own row.
@@ -97,7 +224,10 @@ export interface ChannelStore {
97
224
  * insertion order after arbitrary edits are both effectively arbitrary.
98
225
  * Order explicitly to make succession predictable.
99
226
  */
100
- listReplicas(secretId: string): Promise<Uint8Array | null | undefined>;
227
+ listReplicas(
228
+ secretId: string,
229
+ filter: ReplicaFilter,
230
+ ): Promise<Uint8Array | null | undefined>;
101
231
  linkChannel(
102
232
  secretId: string,
103
233
  channelId: string,
@@ -155,7 +285,7 @@ export interface UserSecretStore {
155
285
  * returns the exact blob it received, `remove` drops the row, and
156
286
  * `loadAll` returns every blob whose `kind` matches the requested
157
287
  * category (`0` = PendingVerification, `1` = PendingRecovery,
158
- * `2` = PendingUnpair, `3` = SharingRound, `4` = PendingSyncCheck).
288
+ * `2` = PendingUnpair, `3` = SharingRound, `4` = PendingReplicaDiscovery).
159
289
  *
160
290
  * Rows are keyed by `(secretId, StateKey)` — the `keyJson` buffer is
161
291
  * a JSON object `{ kind, channel_id?, version? }` matching the `kind`
@@ -197,9 +327,84 @@ export interface StateStore {
197
327
  * DeRec over request/response transports" in the Rust SDK README.
198
328
  */
199
329
  export interface Transport {
200
- send(endpoint: { protocol: string; uri: string }, message: Uint8Array): Promise<void>;
330
+ /**
331
+ * Delivers `message` to a peer reachable at any of `endpoints`.
332
+ *
333
+ * `endpoints` are the addresses that peer advertised, in the order it
334
+ * offered them, already filtered to those the library will record. The
335
+ * library does not rank them: which to dial, and whether to fall back when
336
+ * one is unreachable, is this implementation's choice. Never empty.
337
+ *
338
+ * Delivery to any one endpoint is success. Reject only when the message
339
+ * reached none of them.
340
+ *
341
+ * **Deliver once.** Every entry addresses the same peer, so sending to all
342
+ * of them delivers one authenticated message several times. Stop at the
343
+ * first success. The protocol's handlers are idempotent, so a duplicate
344
+ * does not corrupt state, but it is still a duplicate to anything counting
345
+ * messages, and a peer entitled to treat re-delivery as a replay will.
346
+ *
347
+ * **Prefer an adapter to writing this by hand.** Choosing which endpoint to
348
+ * dial is yours and stays here; the bookkeeping around it is the same
349
+ * everywhere and is already written and tested. Write a
350
+ * {@link SendOne} and wrap it in {@link sequentialFailover}. Taking
351
+ * `endpoints[0]` type-checks, passes every test, and silently gives up the
352
+ * failover the list exists to provide — if that is genuinely wanted, say so
353
+ * with {@link singleEndpointTransport} rather than by indexing.
354
+ */
355
+ send(
356
+ endpoints: ReadonlyArray<{ protocol: string; uri: string }>,
357
+ message: Uint8Array,
358
+ ): Promise<void>;
201
359
  }
202
360
 
361
+ /**
362
+ * Delivers one message to one endpoint.
363
+ *
364
+ * The narrow half of a transport: everything genuinely about dialing, and
365
+ * nothing about which endpoint to dial. Pass one of these to
366
+ * {@link sequentialFailover} or {@link singleEndpointTransport} to get a
367
+ * {@link Transport}.
368
+ *
369
+ * Rejecting means the endpoint did not receive the message. The rejection
370
+ * reason need not distinguish "unreachable" from "rejected":
371
+ * {@link sequentialFailover} treats both as a reason to try the next endpoint,
372
+ * which is the safe reading. Trying an endpoint that would have refused costs
373
+ * a round trip; skipping one that would have worked costs the delivery.
374
+ */
375
+ export type SendOne = (
376
+ endpoint: { protocol: string; uri: string },
377
+ message: Uint8Array,
378
+ ) => Promise<void>;
379
+
380
+ /**
381
+ * Builds a {@link Transport} that tries each endpoint in the order the peer
382
+ * offered it and stops at the first success.
383
+ *
384
+ * An error is thrown only when every endpoint failed. The message is delivered
385
+ * at most once.
386
+ *
387
+ * This is the right default. A peer advertising several endpoints is saying it
388
+ * can be reached at any of them, and the reason 0.0.3 records the whole list is
389
+ * so one being down does not end the conversation.
390
+ */
391
+ export declare function sequentialFailover(dialer: SendOne): Transport;
392
+
393
+ /**
394
+ * Builds a {@link Transport} that uses the first endpoint only.
395
+ *
396
+ * Reproduces the pre-0.0.3 behaviour exactly, for an application that genuinely
397
+ * serves one endpoint or has a reason not to fail over.
398
+ *
399
+ * It exists so that choosing it is visible. `endpoints[0]` written inline looks
400
+ * like an implementation detail and reads as finished; naming this records that
401
+ * failover was considered and declined, which is a claim a reviewer can
402
+ * disagree with. If the peers this application talks to advertise more than one
403
+ * endpoint, prefer {@link sequentialFailover} — every endpoint after the first
404
+ * is reachability being thrown away.
405
+ */
406
+ export declare function singleEndpointTransport(dialer: SendOne): Transport;
407
+
203
408
  export enum SenderKind {
204
409
  Owner = 0,
205
410
  Helper = 1,
@@ -250,12 +455,12 @@ export enum FlowKind {
250
455
  /** Ask the replica group whether this device is behind, and catch up if it
251
456
  * is. Replica-only, and takes no parameters — the group and this device's
252
457
  * own version both come from the stores. */
253
- SyncCheck = 7,
458
+ ReplicaDiscovery = 7,
254
459
  /** Remove a member from the replica group. Replica-only. Naming this device
255
460
  * is a voluntary departure; naming another is an eviction. Params:
256
461
  * `{ replica_id: string; memo?: string }` — `replica_id` is a decimal
257
462
  * string so ids above 2^53 survive JS number handling. */
258
- RemoveReplica = 8,
463
+ UnpairReplica = 8,
259
464
  }
260
465
 
261
466
  export type UnpairAck = "required" | "not_required";
@@ -264,6 +469,13 @@ export interface ContactMessage {
264
469
  channel_id: bigint;
265
470
  /** `ContactMode` numeric value (0 = INLINE_KEYS, 1 = HASHED_KEYS, 2 = NO_KEYS). */
266
471
  contact_mode: number;
472
+ /**
473
+ * @deprecated Reading this field directly is incorrect: its meaning narrowed
474
+ * to "one entry of a list, and possibly absent", so a peer advertising only
475
+ * `supported_transports` looks unreachable to a reader that was correct
476
+ * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
477
+ * spellings. Removed at 0.0.5.
478
+ */
267
479
  transport_protocol?: TransportProtocol;
268
480
  nonce: bigint;
269
481
  /** Present only when `contact_mode === ContactMode.InlineKeys`. */
@@ -273,6 +485,10 @@ export interface ContactMessage {
273
485
  /** Present only when `contact_mode === ContactMode.HashedKeys`. SHA-384 digest (48 bytes). */
274
486
  contact_binding_hash?: Uint8Array;
275
487
  timestamp?: Timestamp;
488
+ /** Every transport endpoint the creator of this contact can be reached
489
+ * on, in its own preference order. Empty means "only
490
+ * `transport_protocol` is offered". */
491
+ supported_transports: TransportProtocol[];
276
492
  }
277
493
 
278
494
  export interface UserSecret {
@@ -319,8 +535,20 @@ export interface UpdateChannelInfoParams {
319
535
  * map untouched; pass an empty object to clear it. */
320
536
  communication_info?: Record<string, string>;
321
537
 
322
- /** New transport endpoint. Absent leaves it untouched. */
538
+ /**
539
+ * New transport endpoint. Absent leaves it untouched.
540
+ *
541
+ * @deprecated Use `own_transports`, which carries every endpoint this node
542
+ * now serves; its first entry also fills this field for peers predating the
543
+ * list. Removed at 0.0.5.
544
+ */
323
545
  transport_protocol?: { uri: string; protocol: number };
546
+ /**
547
+ * Every endpoint this node now serves, in its own preference order.
548
+ * Omitted leaves the target(s)' stored set untouched. Takes precedence
549
+ * over `transport_protocol`, whose first entry it also fills.
550
+ */
551
+ own_transports?: TransportProtocol[];
324
552
  }
325
553
 
326
554
  /**
@@ -349,11 +577,11 @@ export interface Timeouts {
349
577
  expired_channels?: { enabled: boolean; timeout_in_secs: number };
350
578
  }
351
579
 
352
- /** `SyncCheck` takes no parameters: the group and this device's own version
580
+ /** `ReplicaDiscovery` takes no parameters: the group and this device's own version
353
581
  * are both read from the stores. The argument may be omitted entirely. */
354
- export type SyncCheckParams = Record<string, never>;
582
+ export type ReplicaDiscoveryParams = Record<string, never>;
355
583
 
356
- export interface RemoveReplicaParams {
584
+ export interface UnpairReplicaParams {
357
585
  /** The member to remove, as a **decimal** `u64` string — the same form
358
586
  * `ReplicaPaired.peer_replica_id` hands back. A value naming no current
359
587
  * member is rejected; it is not silently ignored. */
@@ -378,7 +606,7 @@ export type DeRecEvent =
378
606
 
379
607
  action: Uint8Array;
380
608
 
381
- action_kind: string;
609
+ action_kind: PendingActionKind;
382
610
  peer_communication_info?: Record<string, string>;
383
611
 
384
612
  sender_kind?: SenderKind;
@@ -437,7 +665,7 @@ export type DeRecEvent =
437
665
  /** A replica catch-up finished. `fetched_from` is absent when this device
438
666
  * was already current, in which case no hydration event follows. */
439
667
  | {
440
- type: "SyncCheckComplete";
668
+ type: "ReplicaDiscoveryComplete";
441
669
  local_version: number;
442
670
  group_version: number;
443
671
  fetched_from?: string;
@@ -467,7 +695,8 @@ export type DeRecEvent =
467
695
  secret: {
468
696
  helpers: Array<{
469
697
  channel_id: string;
470
- transport_uri: string;
698
+ /** Every endpoint this peer advertised, in the order it offered them. */
699
+ transports: Array<{ uri: string; protocol: number }>;
471
700
  shared_key: Uint8Array;
472
701
  communication_info: Record<string, string>;
473
702
  }>;
@@ -488,7 +717,8 @@ export type DeRecEvent =
488
717
  * originated, which is why no separate owner field is needed. */
489
718
  members: Array<{
490
719
  replica_id: string;
491
- transport_uri: string;
720
+ /** Every endpoint this peer advertised, in the order it offered them. */
721
+ transports: Array<{ uri: string; protocol: number }>;
492
722
  role: "Source" | "Destination";
493
723
  communication_info: Record<string, string>;
494
724
  }>;
@@ -509,7 +739,7 @@ export type DeRecEvent =
509
739
  /** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
510
740
  * `peer_replica_id` is the peer's `u64` as a **decimal** string,
511
741
  * matching the wire `derec.replica_id` representation and every other
512
- * id across this boundary. Pass it back verbatim — `RemoveReplica`
742
+ * id across this boundary. Pass it back verbatim — `UnpairReplica`
513
743
  * expects the same decimal form. The local side's role
514
744
  * (`ReplicaSource` vs `ReplicaDestination`) is on the persisted
515
745
  * channel record — replica pairings are unidirectional, so there is
@@ -534,7 +764,8 @@ export type DeRecEvent =
534
764
  secret: {
535
765
  helpers: Array<{
536
766
  channel_id: string;
537
- transport_uri: string;
767
+ /** Every endpoint this peer advertised, in the order it offered them. */
768
+ transports: Array<{ uri: string; protocol: number }>;
538
769
  shared_key: Uint8Array;
539
770
  communication_info: Record<string, string>;
540
771
  }>;
@@ -553,7 +784,8 @@ export type DeRecEvent =
553
784
  * originated, which is why no separate owner field is needed. */
554
785
  members: Array<{
555
786
  replica_id: string;
556
- transport_uri: string;
787
+ /** Every endpoint this peer advertised, in the order it offered them. */
788
+ transports: Array<{ uri: string; protocol: number }>;
557
789
  role: "Source" | "Destination";
558
790
  communication_info: Record<string, string>;
559
791
  }>;
@@ -583,7 +815,8 @@ export type DeRecEvent =
583
815
  secret: {
584
816
  helpers: Array<{
585
817
  channel_id: string;
586
- transport_uri: string;
818
+ /** Every endpoint this peer advertised, in the order it offered them. */
819
+ transports: Array<{ uri: string; protocol: number }>;
587
820
  shared_key: Uint8Array;
588
821
  communication_info: Record<string, string>;
589
822
  }>;
@@ -602,7 +835,8 @@ export type DeRecEvent =
602
835
  * originated, which is why no separate owner field is needed. */
603
836
  members: Array<{
604
837
  replica_id: string;
605
- transport_uri: string;
838
+ /** Every endpoint this peer advertised, in the order it offered them. */
839
+ transports: Array<{ uri: string; protocol: number }>;
606
840
  role: "Source" | "Destination";
607
841
  communication_info: Record<string, string>;
608
842
  }>;
@@ -649,21 +883,21 @@ export type DeRecEvent =
649
883
  * for observability — no further action is required. `action_kind`
650
884
  * is the same label vocabulary as `ActionRequired.action_kind`
651
885
  * (`"Pairing"`, `"StoreShare"`, …). */
652
- | { type: "AutoAccepted"; channel_id: string; action_kind: string }
886
+ | { type: "AutoAccepted"; channel_id: string; action_kind: PendingActionKind }
653
887
  | { type: "NoOp" }
654
888
  /** A pairing handshake was dispatched successfully. `kind` is the
655
889
  * local party's role — same value the subsequent `PairingCompleted`
656
890
  * will carry. Emitted by `start(Pairing)`. */
657
- | { type: "PairingStarted"; channel_id: string; kind: SenderKind }
891
+ | { type: "PairingStarted"; channel_id: string; kind: SenderKind; trace_id: string }
658
892
  /** A discovery request was dispatched to `channel_id`. Emitted per
659
893
  * targeted helper by `start(Discovery)`. */
660
- | { type: "DiscoveryStarted"; channel_id: string }
894
+ | { type: "DiscoveryStarted"; channel_id: string; trace_id: string }
661
895
  /** A discovery request could not be dispatched to `channel_id`. Other
662
896
  * targeted channels are unaffected. */
663
897
  | { type: "DiscoveryFailed"; channel_id: string; error: string }
664
898
  /** A share-storage request was dispatched to `channel_id`. Emitted per
665
899
  * targeted peer by `start(ProtectSecret)`. */
666
- | { type: "ProtectSecretStarted"; channel_id: string; version: number }
900
+ | { type: "ProtectSecretStarted"; channel_id: string; version: number; trace_id: string }
667
901
  /** A share-storage request could not be dispatched to `channel_id`. */
668
902
  | {
669
903
  type: "ProtectSecretFailed";
@@ -672,7 +906,7 @@ export type DeRecEvent =
672
906
  error: string;
673
907
  }
674
908
  /** A verify-share challenge was dispatched to `channel_id`. */
675
- | { type: "VerifySharesStarted"; channel_id: string; version: number }
909
+ | { type: "VerifySharesStarted"; channel_id: string; version: number; trace_id: string }
676
910
  /** A verify-share challenge could not be dispatched to `channel_id`. */
677
911
  | {
678
912
  type: "VerifySharesFailed";
@@ -681,7 +915,7 @@ export type DeRecEvent =
681
915
  error: string;
682
916
  }
683
917
  /** A recovery share request was dispatched to `channel_id`. */
684
- | { type: "RecoverSecretStarted"; channel_id: string; version: number }
918
+ | { type: "RecoverSecretStarted"; channel_id: string; version: number; trace_id: string }
685
919
  /** A recovery share request could not be dispatched to `channel_id`. */
686
920
  | {
687
921
  type: "RecoverSecretFailed";
@@ -693,9 +927,9 @@ export type DeRecEvent =
693
927
  * `Unpaired` event once the peer acknowledges (or in the same event
694
928
  * vec, under `UnpairAck.NotRequired`). */
695
929
  | { type: "UnpairFailed"; channel_id: string; error: string }
696
- | { type: "UnpairStarted"; channel_id: string }
930
+ | { type: "UnpairStarted"; channel_id: string; trace_id: string }
697
931
  /** An update-channel-info request was dispatched to `channel_id`. */
698
- | { type: "UpdateChannelInfoStarted"; channel_id: string }
932
+ | { type: "UpdateChannelInfoStarted"; channel_id: string; trace_id: string }
699
933
  /** An update-channel-info request could not be dispatched to
700
934
  * `channel_id`. */
701
935
  | { type: "UpdateChannelInfoFailed"; channel_id: string; error: string };
@@ -748,8 +982,8 @@ export interface AutoAcceptPolicy {
748
982
  * between them without reaching for reference docs.
749
983
  *
750
984
  * Required setters: `withChannelStore`, `withShareStore`,
751
- * `withSecretStore`, `withTransport`, `withOwnTransport`. Calling
752
- * `build()` without all five throws.
985
+ * `withSecretStore`, `withTransport`, and either `withOwnTransport` or
986
+ * `withOwnTransports`. Calling `build()` without all five throws.
753
987
  */
754
988
  export declare class DeRecProtocolBuilder {
755
989
  /**
@@ -766,7 +1000,28 @@ export declare class DeRecProtocolBuilder {
766
1000
  withUserSecretStore(store: UserSecretStore): DeRecProtocolBuilder;
767
1001
  withStateStore(store: StateStore): DeRecProtocolBuilder;
768
1002
  withTransport(transport: Transport): DeRecProtocolBuilder;
1003
+ /**
1004
+ * @deprecated Use {@link withOwnTransports}, which takes the whole
1005
+ * preference list — `withOwnTransports([endpoint])` is the direct
1006
+ * replacement. Removed at 0.0.5.
1007
+ */
769
1008
  withOwnTransport(endpoint: { uri: string; protocol: string }): DeRecProtocolBuilder;
1009
+ /**
1010
+ * Set every transport endpoint this application serves, in preference
1011
+ * order. `protocol` is `"https"` or `"grpc"` (case-insensitive) per
1012
+ * entry, same as {@link withOwnTransport}.
1013
+ *
1014
+ * The order is this application's own preference and decides which of
1015
+ * a peer's offered endpoints is used; it is not sorted, deduplicated,
1016
+ * or reordered. Every listed transport must actually be served,
1017
+ * because delivery is push-only — listing an endpoint this application
1018
+ * does not serve makes pairing succeed and replies vanish.
1019
+ *
1020
+ * Supersedes {@link withOwnTransport} for applications serving more
1021
+ * than one transport; the single-endpoint setter remains fully
1022
+ * supported.
1023
+ */
1024
+ withOwnTransports(transports: { uri: string; protocol: string }[]): DeRecProtocolBuilder;
770
1025
 
771
1026
  /** Default: 3. */
772
1027
  withThreshold(threshold: number): DeRecProtocolBuilder;
@@ -806,8 +1061,22 @@ export declare class DeRecProtocolBuilder {
806
1061
  * delivery is your `Transport`. Nothing here stops an application sending
807
1062
  * plaintext — it governs which endpoints the protocol will record,
808
1063
  * propagate to peers, and reply to.
1064
+ *
1065
+ * @deprecated Use {@link withUnsafeConnection}, which names both gated
1066
+ * schemes. Removed at 0.0.5.
809
1067
  */
810
1068
  withUnsafeHttp(allow: boolean): DeRecProtocolBuilder;
1069
+ /**
1070
+ * Accept plaintext `http://` and `grpc://` transport endpoints.
1071
+ * **Development only.** Default: `false`. Supersedes
1072
+ * {@link withUnsafeHttp}, which names only the HTTP scheme.
1073
+ *
1074
+ * Either flag alone is honored. Setting both to disagreeing values fails
1075
+ * construction with the error code `CONFLICTING_PLAINTEXT_OPT_IN` rather
1076
+ * than resolving silently, because precedence would hand the decision to
1077
+ * the flag being removed.
1078
+ */
1079
+ withUnsafeConnection(allow: boolean): DeRecProtocolBuilder;
811
1080
  /** Default: empty. */
812
1081
  withCommunicationInfo(info: Record<string, string>): DeRecProtocolBuilder;
813
1082
  /** Default: false. */
@@ -836,6 +1105,18 @@ export declare class DeRecProtocolBuilder {
836
1105
  * stable across restarts. Default: unset.
837
1106
  */
838
1107
  withReplicaId(id: bigint | number): DeRecProtocolBuilder;
1108
+ /**
1109
+ * Declare the bounds this node advertises during pair negotiation.
1110
+ *
1111
+ * Embedded in outbound `PairRequest` / `PairResponse` envelopes and
1112
+ * checked against the peer's range on inbound ones: a range that fails to
1113
+ * intersect rejects the pairing. Keys match the {@link ParameterRange}
1114
+ * interface; every field is optional and defaults to `0`, which the
1115
+ * protocol reads as no constraint on that dimension.
1116
+ *
1117
+ * Default: unset — no constraints advertised, every peer range accepted.
1118
+ */
1119
+ withParameterRange(range: Partial<ParameterRange>): DeRecProtocolBuilder;
839
1120
 
840
1121
  /**
841
1122
  * Finalize the configuration. Throws if any of the required setters
@@ -889,7 +1170,7 @@ export declare class DeRecProtocol {
889
1170
  start(flowKind: FlowKind.RecoverSecret, params: RecoverSecretParams): Promise<DeRecEvent[]>;
890
1171
  start(flowKind: FlowKind.Unpair, params: UnpairParams): Promise<DeRecEvent[]>;
891
1172
  start(flowKind: FlowKind.UpdateChannelInfo, params: UpdateChannelInfoParams): Promise<DeRecEvent[]>;
892
- start(flowKind: FlowKind.SyncCheck, params?: SyncCheckParams): Promise<DeRecEvent[]>;
1173
+ start(flowKind: FlowKind.ReplicaDiscovery, params?: ReplicaDiscoveryParams): Promise<DeRecEvent[]>;
893
1174
 
894
1175
  /** Announce a member's removal. This does **not** remove anything on its
895
1176
  * own and emits no `ReplicaRemoved`: it tells every member and flags the
@@ -898,7 +1179,7 @@ export declare class DeRecProtocol {
898
1179
  * `start(FlowKind.ProtectSecret)` — at which point `ReplicaRemoved`
899
1180
  * fires. A group with no secret to publish therefore cannot complete a
900
1181
  * removal. */
901
- start(flowKind: FlowKind.RemoveReplica, params: RemoveReplicaParams): Promise<DeRecEvent[]>;
1182
+ start(flowKind: FlowKind.UnpairReplica, params: UnpairReplicaParams): Promise<DeRecEvent[]>;
902
1183
 
903
1184
  /**
904
1185
  * Replace this node's local <c>communication_info</c> map. Does not
@@ -908,12 +1189,36 @@ export declare class DeRecProtocol {
908
1189
  setCommunicationInfo(info: Record<string, string>): void;
909
1190
 
910
1191
  /**
911
- * Replace this node's local transport endpoint. IMPORTANT: keep the
912
- * old endpoint operational during the changeover (see the Rust docs
913
- * on the matching setter for the discipline).
1192
+ * Replace this node's endpoint for one protocol, leaving the others
1193
+ * alone. A node serves at most one endpoint per protocol, so the
1194
+ * `(uri, protocol)` pair identifies the entry it replaces; an entry for
1195
+ * a protocol not yet served is appended, and a replaced one keeps its
1196
+ * position in the preference order.
1197
+ *
1198
+ * IMPORTANT: keep the old endpoint operational during the changeover
1199
+ * (see the Rust docs on the matching setter for the discipline).
1200
+ *
1201
+ * @deprecated Use {@link setOwnTransports}, which takes the whole
1202
+ * preference list and is the only way to change which protocols this
1203
+ * node serves, or their order. Removed at 0.0.5.
914
1204
  */
915
1205
  setOwnTransport(uri: string, protocol: string): void;
916
1206
 
1207
+ /**
1208
+ * Replace every endpoint this node advertises, in preference order —
1209
+ * the runtime counterpart to <c>withOwnTransports</c>, and the way to
1210
+ * change the whole set (<c>setOwnTransport</c> replaces only the entry
1211
+ * for the protocol its URI names).
1212
+ *
1213
+ * A node serves at most one endpoint per protocol, so this list is a
1214
+ * preference order over distinct protocols. Two entries of the same
1215
+ * protocol are rejected.
1216
+ *
1217
+ * Every entry is validated before any is stored, so a malformed URI
1218
+ * leaves the previous set intact. An empty array is rejected.
1219
+ */
1220
+ setOwnTransports(transports: { uri: string; protocol: string }[]): void;
1221
+
917
1222
  process(message: Uint8Array): Promise<DeRecEvent[]>;
918
1223
 
919
1224
  /**
@@ -1017,7 +1322,14 @@ export interface GetSecretIdsVersionsRequestMessage {
1017
1322
  timestamp?: Timestamp;
1018
1323
  /** Ephemeral endpoint where the requester wants the response routed.
1019
1324
  * Absent means "use the channel's stored peer endpoint". */
1020
- reply_to?: TransportProtocol;
1325
+ /**
1326
+ * Every endpoint the requester can be answered on for this exchange, in
1327
+ * its own preference order. Omitted means route to the endpoints already
1328
+ * recorded for the channel.
1329
+ */
1330
+ reply_to?: TransportProtocol[];
1331
+ /** Replica-group member that sent this; see `replicaId` semantics. */
1332
+ replica_id?: bigint;
1021
1333
  }
1022
1334
 
1023
1335
  export interface VersionList {
@@ -1034,6 +1346,8 @@ export interface GetSecretIdsVersionsResponseMessage {
1034
1346
  result?: DeRecResult;
1035
1347
  secret_list: VersionList[];
1036
1348
  timestamp?: Timestamp;
1349
+ /** Replica-group member that sent this; see `replicaId` semantics. */
1350
+ replica_id?: bigint;
1037
1351
  }
1038
1352
 
1039
1353
  export interface VersionEntry {
@@ -1066,6 +1380,22 @@ export interface CommunicationInfo {
1066
1380
  // `INLINE_KEYS` and `HASHED_KEYS` modes.
1067
1381
 
1068
1382
 
1383
+ /**
1384
+ * The label vocabulary for `ActionRequired.action_kind` and
1385
+ * `AutoAccepted.action_kind` — one value per pending-action kind the
1386
+ * protocol can raise. Matches the Rust `PendingActionKind` discriminants
1387
+ * one-for-one.
1388
+ */
1389
+ export type PendingActionKind =
1390
+ | "Pairing"
1391
+ | "PrePair"
1392
+ | "StoreShare"
1393
+ | "VerifyShare"
1394
+ | "Discovery"
1395
+ | "GetShare"
1396
+ | "Unpair"
1397
+ | "UpdateChannelInfo";
1398
+
1069
1399
  export interface ParameterRange {
1070
1400
  min_share_size: bigint;
1071
1401
  max_share_size: bigint;
@@ -1086,8 +1416,18 @@ export interface PairRequestMessage {
1086
1416
  nonce: bigint;
1087
1417
  communication_info?: CommunicationInfo;
1088
1418
  parameter_range?: ParameterRange;
1419
+ /**
1420
+ * @deprecated Reading this field directly is incorrect: its meaning narrowed
1421
+ * to "one entry of a list, and possibly absent", so a peer advertising only
1422
+ * `supported_transports` looks unreachable to a reader that was correct
1423
+ * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
1424
+ * spellings. Removed at 0.0.5.
1425
+ */
1089
1426
  transport_protocol?: TransportProtocol;
1090
1427
  timestamp?: Timestamp;
1428
+ /** Every transport endpoint the initiator can be reached on, in its own
1429
+ * preference order. Empty means "only `transport_protocol` is offered". */
1430
+ supported_transports: TransportProtocol[];
1091
1431
  }
1092
1432
 
1093
1433
  export interface PairResponseMessage {
@@ -1108,8 +1448,21 @@ export interface PairResponseMessage {
1108
1448
 
1109
1449
  export interface PrePairRequestMessage {
1110
1450
  nonce: bigint;
1451
+ /**
1452
+ * @deprecated Reading this field directly is incorrect: its meaning narrowed
1453
+ * to "one entry of a list, and possibly absent", so a peer advertising only
1454
+ * `supported_transports` looks unreachable to a reader that was correct
1455
+ * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
1456
+ * spellings. Removed at 0.0.5.
1457
+ */
1111
1458
  transport_protocol?: TransportProtocol;
1112
1459
  timestamp?: Timestamp;
1460
+ /**
1461
+ * Every endpoint the sender can be reached on for the PrePair reply, in
1462
+ * its own preference order. At least one of this and `transport_protocol`
1463
+ * must be present.
1464
+ */
1465
+ supported_transports?: TransportProtocol[];
1113
1466
  }
1114
1467
 
1115
1468
  export interface PrePairResponseMessage {
@@ -1127,7 +1480,14 @@ export interface GetShareRequestMessage {
1127
1480
  version: number;
1128
1481
  timestamp?: Timestamp;
1129
1482
  /** Ephemeral response endpoint; see `replyTo` semantics. */
1130
- reply_to?: TransportProtocol;
1483
+ /**
1484
+ * Every endpoint the requester can be answered on for this exchange, in
1485
+ * its own preference order. Omitted means route to the endpoints already
1486
+ * recorded for the channel.
1487
+ */
1488
+ reply_to?: TransportProtocol[];
1489
+ /** Replica-group member that sent this; see `replicaId` semantics. */
1490
+ replica_id?: bigint;
1131
1491
  }
1132
1492
 
1133
1493
  export interface GetShareResponseMessage {
@@ -1141,6 +1501,8 @@ export interface GetShareResponseMessage {
1141
1501
  secret_id: bigint;
1142
1502
  /** Echoed from the request for the same correlation reasons as `secret_id`. */
1143
1503
  version: number;
1504
+ /** Replica-group member that sent this; see `replicaId` semantics. */
1505
+ replica_id?: bigint;
1144
1506
  }
1145
1507
 
1146
1508
  export interface SiblingHash {
@@ -1165,7 +1527,14 @@ export interface StoreShareRequestMessage {
1165
1527
  timestamp?: Timestamp;
1166
1528
  secret_id: bigint;
1167
1529
  /** Ephemeral response endpoint; see `replyTo` semantics. */
1168
- reply_to?: TransportProtocol;
1530
+ /**
1531
+ * Every endpoint the requester can be answered on for this exchange, in
1532
+ * its own preference order. Omitted means route to the endpoints already
1533
+ * recorded for the channel.
1534
+ */
1535
+ reply_to?: TransportProtocol[];
1536
+ /** Replica-group member that sent this; see `replicaId` semantics. */
1537
+ replica_id?: bigint;
1169
1538
  }
1170
1539
 
1171
1540
  export interface StoreShareResponseMessage {
@@ -1173,13 +1542,22 @@ export interface StoreShareResponseMessage {
1173
1542
  version: number;
1174
1543
  timestamp?: Timestamp;
1175
1544
  secret_id: bigint;
1545
+ /** Replica-group member that sent this; see `replicaId` semantics. */
1546
+ replica_id?: bigint;
1176
1547
  }
1177
1548
 
1178
1549
  export interface UnpairRequestMessage {
1179
1550
  memo: string;
1180
1551
  timestamp?: Timestamp;
1181
1552
  /** Ephemeral response endpoint; see `replyTo` semantics. */
1182
- reply_to?: TransportProtocol;
1553
+ /**
1554
+ * Every endpoint the requester can be answered on for this exchange, in
1555
+ * its own preference order. Omitted means route to the endpoints already
1556
+ * recorded for the channel.
1557
+ */
1558
+ reply_to?: TransportProtocol[];
1559
+ /** Replica-group member that sent this; see `replicaId` semantics. */
1560
+ replica_id?: bigint;
1183
1561
  }
1184
1562
 
1185
1563
  export interface UnpairResponseMessage {
@@ -1193,7 +1571,12 @@ export interface VerifyShareRequestMessage {
1193
1571
  nonce: bigint;
1194
1572
  timestamp?: Timestamp;
1195
1573
  /** Ephemeral response endpoint; see `replyTo` semantics. */
1196
- reply_to?: TransportProtocol;
1574
+ /**
1575
+ * Every endpoint the requester can be answered on for this exchange, in
1576
+ * its own preference order. Omitted means route to the endpoints already
1577
+ * recorded for the channel.
1578
+ */
1579
+ reply_to?: TransportProtocol[];
1197
1580
  }
1198
1581
 
1199
1582
  export interface VerifyShareResponseMessage {
@@ -1229,7 +1612,12 @@ export interface PairingRequestProduceResult extends ProduceResult {
1229
1612
  }
1230
1613
 
1231
1614
  export interface PairingResponseProduceResult extends ProduceResult {
1232
- peer_transport_protocol: TransportProtocol;
1615
+ /**
1616
+ * Every endpoint the requester advertised, in the order it offered them,
1617
+ * filtered to those the library will record. Never empty. Choosing which
1618
+ * to dial, and failing over when one is unreachable, is the application's.
1619
+ */
1620
+ peer_transports: TransportProtocol[];
1233
1621
 
1234
1622
  shared_key: Uint8Array;
1235
1623
 
@@ -1338,7 +1726,7 @@ export declare const primitives: {
1338
1726
  produce(
1339
1727
  channel_id: bigint,
1340
1728
  shared_key: Uint8Array,
1341
- reply_to?: TransportProtocol | null,
1729
+ reply_to?: TransportProtocol[],
1342
1730
  ): ProduceResult;
1343
1731
  extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: GetSecretIdsVersionsRequestMessage };
1344
1732
  };
@@ -1358,19 +1746,21 @@ export declare const primitives: {
1358
1746
  * `ContactMode.HashedKeys` embeds only a SHA-384
1359
1747
  * commitment and the scanner must complete a
1360
1748
  * `PrePair` round-trip first.
1361
- * @param transport_protocol Endpoint the scanner uses to talk back. For
1362
- * `HashedKeys` mode it MUST be ephemeral.
1749
+ * @param transport_protocols Every endpoint this initiator serves, in
1750
+ * preference order. The first also fills the
1751
+ * legacy singular field. For `HashedKeys`
1752
+ * mode they MUST be ephemeral.
1363
1753
  */
1364
1754
  create_contact(
1365
1755
  channel_id: bigint,
1366
1756
  contact_mode: ContactMode | number,
1367
- transport_protocol: TransportProtocol,
1757
+ transport_protocols: TransportProtocol[],
1368
1758
  ): CreateContactResult;
1369
1759
  encode_contact(contact_message: ContactMessage): Uint8Array;
1370
1760
  decode_contact(bytes: Uint8Array): ContactMessage;
1371
1761
  produce(
1372
1762
  kind: SenderKind,
1373
- transport_protocol: TransportProtocol,
1763
+ transport_protocols: TransportProtocol[],
1374
1764
  contact_message: ContactMessage,
1375
1765
  communication_info: CommunicationInfo | null,
1376
1766
  parameter_range: ParameterRange | null,
@@ -1385,8 +1775,14 @@ export declare const primitives: {
1385
1775
  * binding hash with `pairing.response.process_pre_pair` before
1386
1776
  * proceeding to a normal `produce`.
1387
1777
  */
1778
+ /**
1779
+ * @param own_transports Every endpoint this scanner serves for the
1780
+ * PrePair reply, in its own preference order.
1781
+ * The first entry also fills the deprecated
1782
+ * singular field for peers predating the list.
1783
+ */
1388
1784
  produce_pre_pair(
1389
- transport_protocol: TransportProtocol,
1785
+ own_transports: TransportProtocol[],
1390
1786
  contact_message: ContactMessage,
1391
1787
  ): ProducePrePairResult;
1392
1788
 
@@ -1397,12 +1793,17 @@ export declare const primitives: {
1397
1793
  extract_pre_pair(envelope_bytes: Uint8Array): PrePairRequestExtractResult;
1398
1794
  };
1399
1795
  response: {
1796
+ /**
1797
+ * @param unsafe_connection Accept plaintext peer endpoints
1798
+ * (`http://`, `grpc://`). Development only.
1799
+ */
1400
1800
  produce(
1401
1801
  channel_id: bigint,
1402
1802
  request: PairRequestMessage,
1403
1803
  secret_key: Uint8Array,
1404
1804
  communication_info: CommunicationInfo | null,
1405
1805
  parameter_range: ParameterRange | null,
1806
+ unsafe_connection?: boolean,
1406
1807
  ): PairingResponseProduceResult;
1407
1808
 
1408
1809
  extract(envelope_bytes: Uint8Array, secret_key: Uint8Array): { response: PairResponseMessage };
@@ -1447,7 +1848,7 @@ export declare const primitives: {
1447
1848
  version: number,
1448
1849
  shared_key: Uint8Array,
1449
1850
  /** See `discovery.request.produce.reply_to`. */
1450
- reply_to?: TransportProtocol | null,
1851
+ reply_to?: TransportProtocol[],
1451
1852
  ): ProduceResult;
1452
1853
  extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: GetShareRequestMessage };
1453
1854
  };
@@ -1480,7 +1881,7 @@ export declare const primitives: {
1480
1881
  description: string,
1481
1882
  shared_key: Uint8Array,
1482
1883
  /** See `discovery.request.produce.reply_to`. */
1483
- reply_to?: TransportProtocol | null,
1884
+ reply_to?: TransportProtocol[],
1484
1885
  ): ProduceResult;
1485
1886
  extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: StoreShareRequestMessage };
1486
1887
  };
@@ -1501,7 +1902,7 @@ export declare const primitives: {
1501
1902
  memo: string,
1502
1903
  shared_key: Uint8Array,
1503
1904
  /** See `discovery.request.produce.reply_to`. */
1504
- reply_to?: TransportProtocol | null,
1905
+ reply_to?: TransportProtocol[],
1505
1906
  ): ProduceResult;
1506
1907
  extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: UnpairRequestMessage };
1507
1908
  };
@@ -1519,7 +1920,7 @@ export declare const primitives: {
1519
1920
  version: number,
1520
1921
  shared_key: Uint8Array,
1521
1922
  /** See `discovery.request.produce.reply_to`. */
1522
- reply_to?: TransportProtocol | null,
1923
+ reply_to?: TransportProtocol[],
1523
1924
  ): ProduceResult;
1524
1925
  extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: VerifyShareRequestMessage };
1525
1926
  };