@kubun/plugin-p2p 0.11.0 → 0.12.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.
Files changed (156) hide show
  1. package/lib/context/group.js +1073 -746
  2. package/lib/context/hub.js +2 -1
  3. package/lib/context/join.js +50 -15
  4. package/lib/context/peer.d.ts +92 -0
  5. package/lib/context/peer.js +1216 -0
  6. package/lib/context/require-admin.d.ts +57 -0
  7. package/lib/context/require-admin.js +57 -0
  8. package/lib/context/sync.d.ts +12 -0
  9. package/lib/context/sync.js +234 -1
  10. package/lib/context/types.d.ts +56 -20
  11. package/lib/context/types.js +23 -3
  12. package/lib/groups/access-default-token.d.ts +44 -0
  13. package/lib/groups/access-default-token.js +103 -0
  14. package/lib/groups/admin-roster.d.ts +42 -117
  15. package/lib/groups/admin-roster.js +53 -168
  16. package/lib/groups/anchor-store.d.ts +15 -0
  17. package/lib/groups/anchor-store.js +37 -0
  18. package/lib/groups/app-cursor-store.d.ts +17 -0
  19. package/lib/groups/app-cursor-store.js +34 -0
  20. package/lib/groups/broadcast.d.ts +199 -81
  21. package/lib/groups/broadcast.js +443 -368
  22. package/lib/groups/catalog-token.d.ts +50 -0
  23. package/lib/groups/catalog-token.js +96 -0
  24. package/lib/groups/circle-projection.d.ts +32 -64
  25. package/lib/groups/circle-projection.js +95 -181
  26. package/lib/groups/circle-reducers.d.ts +71 -110
  27. package/lib/groups/circle-reducers.js +57 -127
  28. package/lib/groups/commit-adoption.d.ts +77 -0
  29. package/lib/groups/commit-adoption.js +108 -0
  30. package/lib/groups/commit-journal.d.ts +14 -0
  31. package/lib/groups/commit-journal.js +65 -0
  32. package/lib/groups/control-request.d.ts +66 -0
  33. package/lib/groups/control-request.js +101 -0
  34. package/lib/groups/events.d.ts +14 -1
  35. package/lib/groups/group-anchor.d.ts +18 -57
  36. package/lib/groups/group-anchor.js +15 -100
  37. package/lib/groups/group-crypto.d.ts +36 -13
  38. package/lib/groups/group-crypto.js +124 -50
  39. package/lib/groups/group-handle-registry.d.ts +106 -36
  40. package/lib/groups/group-handle-registry.js +351 -61
  41. package/lib/groups/group-handlers.d.ts +5 -4
  42. package/lib/groups/group-handlers.js +155 -29
  43. package/lib/groups/group-health-monitor.d.ts +1 -1
  44. package/lib/groups/group-health-monitor.js +1 -1
  45. package/lib/groups/group-mls.d.ts +35 -4
  46. package/lib/groups/group-mls.js +594 -62
  47. package/lib/groups/group-peer-manager.d.ts +212 -19
  48. package/lib/groups/group-peer-manager.js +1061 -69
  49. package/lib/groups/group-protocols.d.ts +485 -28
  50. package/lib/groups/group-protocols.js +289 -15
  51. package/lib/groups/invite-payload.d.ts +48 -0
  52. package/lib/groups/join-request-identity.d.ts +21 -0
  53. package/lib/groups/join-request-identity.js +43 -0
  54. package/lib/groups/join-utils.d.ts +60 -0
  55. package/lib/groups/join-utils.js +262 -0
  56. package/lib/groups/ledger-affected-events.d.ts +11 -6
  57. package/lib/groups/ledger-affected-events.js +15 -26
  58. package/lib/groups/ledger-commit-fold.d.ts +107 -0
  59. package/lib/groups/ledger-commit-fold.js +167 -0
  60. package/lib/groups/ledger-ingest.d.ts +124 -0
  61. package/lib/groups/ledger-ingest.js +212 -0
  62. package/lib/groups/ledger.d.ts +44 -0
  63. package/lib/groups/ledger.js +30 -0
  64. package/lib/groups/manager.d.ts +202 -19
  65. package/lib/groups/manager.js +623 -106
  66. package/lib/groups/mls-codec.d.ts +27 -6
  67. package/lib/groups/mls-codec.js +30 -15
  68. package/lib/groups/mls-encryptor.js +9 -6
  69. package/lib/groups/mls-group-handle.d.ts +37 -2
  70. package/lib/groups/mls-group-handle.js +12 -4
  71. package/lib/groups/mls-receive-errors.d.ts +0 -2
  72. package/lib/groups/mls-receive-errors.js +3 -6
  73. package/lib/groups/mls-state.d.ts +5 -3
  74. package/lib/groups/mls-state.js +27 -4
  75. package/lib/groups/peer-presence.d.ts +90 -0
  76. package/lib/groups/peer-presence.js +295 -0
  77. package/lib/groups/peer-selection.d.ts +37 -0
  78. package/lib/groups/peer-selection.js +72 -0
  79. package/lib/groups/roster-projection.d.ts +53 -0
  80. package/lib/groups/roster-projection.js +65 -0
  81. package/lib/groups/store-received-grant.d.ts +4 -3
  82. package/lib/groups/store-received-grant.js +80 -35
  83. package/lib/groups/store-received-revocation.d.ts +16 -13
  84. package/lib/groups/store-received-revocation.js +23 -22
  85. package/lib/hub/http-client.d.ts +10 -27
  86. package/lib/hub/http-client.js +2 -19
  87. package/lib/hub/hub-like.d.ts +57 -6
  88. package/lib/hub/hub-like.js +539 -52
  89. package/lib/hub/loopback-log-hub.d.ts +20 -0
  90. package/lib/hub/loopback-log-hub.js +178 -0
  91. package/lib/hub/manager.d.ts +25 -40
  92. package/lib/hub/manager.js +51 -94
  93. package/lib/hub/peer-scoped-hub-view.d.ts +35 -0
  94. package/lib/hub/peer-scoped-hub-view.js +92 -0
  95. package/lib/hub/sender-scoped-hub-view.d.ts +41 -0
  96. package/lib/hub/sender-scoped-hub-view.js +59 -0
  97. package/lib/hub/server-did.d.ts +67 -0
  98. package/lib/hub/server-did.js +131 -0
  99. package/lib/hub/wiring.d.ts +71 -14
  100. package/lib/hub/wiring.js +80 -23
  101. package/lib/index.d.ts +48 -13
  102. package/lib/index.js +257 -86
  103. package/lib/peer/connection-registry.d.ts +45 -0
  104. package/lib/peer/connection-registry.js +52 -0
  105. package/lib/peer/handlers.d.ts +69 -0
  106. package/lib/peer/handlers.js +110 -0
  107. package/lib/protocol.d.ts +213 -0
  108. package/lib/protocol.js +268 -0
  109. package/lib/schema.d.ts +2 -1
  110. package/lib/schema.js +805 -131
  111. package/lib/sync/access-default-sender.d.ts +23 -0
  112. package/lib/sync/access-default-sender.js +80 -0
  113. package/lib/sync/authorize.d.ts +57 -3
  114. package/lib/sync/authorize.js +45 -4
  115. package/lib/sync/broadcast-queue.d.ts +3 -4
  116. package/lib/sync/broadcast-queue.js +2 -3
  117. package/lib/sync/catalog-scope.d.ts +8 -7
  118. package/lib/sync/catalog-scope.js +55 -39
  119. package/lib/sync/errors.d.ts +9 -0
  120. package/lib/sync/errors.js +10 -0
  121. package/lib/sync/forwarder.d.ts +13 -19
  122. package/lib/sync/forwarder.js +13 -19
  123. package/lib/sync/handlers.js +54 -14
  124. package/lib/sync/hub-tunnel-sync-listener.d.ts +16 -6
  125. package/lib/sync/hub-tunnel-sync-listener.js +126 -37
  126. package/lib/sync/hub-tunnel-sync-provider.d.ts +31 -11
  127. package/lib/sync/hub-tunnel-sync-provider.js +68 -19
  128. package/lib/sync/merkle-channel.d.ts +9 -1
  129. package/lib/sync/merkle-channel.js +0 -0
  130. package/lib/sync/peer-registry.d.ts +14 -10
  131. package/lib/sync/peer-registry.js +0 -25
  132. package/lib/sync/sync-client.d.ts +11 -1
  133. package/lib/sync/sync-client.js +72 -10
  134. package/lib/sync/sync-manager.d.ts +22 -4
  135. package/lib/sync/sync-manager.js +39 -10
  136. package/lib/sync/tunnel-listeners.d.ts +52 -0
  137. package/lib/sync/tunnel-listeners.js +175 -0
  138. package/lib/sync/tunnel-observability.d.ts +25 -0
  139. package/lib/sync/tunnel-observability.js +63 -0
  140. package/lib/sync/tunnel-topics.d.ts +20 -0
  141. package/lib/sync/tunnel-topics.js +28 -0
  142. package/lib/types.d.ts +699 -28
  143. package/lib/types.js +0 -1
  144. package/lib/util/handler-error.d.ts +16 -0
  145. package/lib/util/handler-error.js +64 -0
  146. package/lib/util/mutex.d.ts +14 -1
  147. package/lib/util/mutex.js +37 -3
  148. package/package.json +59 -52
  149. package/lib/groups/ledger-entry.d.ts +0 -37
  150. package/lib/groups/ledger-entry.js +0 -56
  151. package/lib/groups/ledger-fold.d.ts +0 -56
  152. package/lib/groups/ledger-fold.js +0 -53
  153. package/lib/hub/did-observing-transport.d.ts +0 -75
  154. package/lib/hub/did-observing-transport.js +0 -126
  155. package/lib/hub/errors.d.ts +0 -24
  156. package/lib/hub/errors.js +0 -33
@@ -1,7 +1,28 @@
1
1
  import type { GroupHandle } from '@kumiai/mls';
2
- export type MLSEncryptFramedResult = {
3
- framed: Uint8Array;
4
- consumed: Array<Uint8Array>;
5
- };
6
- export declare function mlsEncryptFramed(handle: GroupHandle, plaintext: Uint8Array): Promise<MLSEncryptFramedResult>;
7
- export declare function mlsDecryptFramed(handle: GroupHandle, framed: Uint8Array): Promise<Uint8Array>;
2
+ /**
3
+ * Encrypt an application message for the group. `@kumiai/mls` returns framed MLS
4
+ * wire bytes, so kubun puts those on the wire directly — no JSON envelope of its
5
+ * own, and no retired secret material crossing the boundary.
6
+ */
7
+ export declare function mlsEncryptFramed(handle: GroupHandle, plaintext: Uint8Array): Promise<Uint8Array>;
8
+ /**
9
+ * Decrypt a received application message and recover WHO SENT IT.
10
+ *
11
+ * `handle.decrypt` rather than `processMessage`, which returns an application
12
+ * message's plaintext with no sender. The sender is AUTHENTICATED, not claimed:
13
+ * the leaf index rides sender-data encrypted under the epoch's sender-data
14
+ * secret, and the body opens only under the ratchet key derived at that leaf, so
15
+ * a frame naming a leaf it was not sealed at does not open at all.
16
+ *
17
+ * A missing sender is an ERROR, never a filled-in blank. Upstream types
18
+ * `senderDID` optional because the authenticated leaf may hold no parsable
19
+ * credential — "I cannot name the author", never "there is no author". Any
20
+ * placeholder (empty string, a transport-claimed DID) would hand callers an
21
+ * unauthenticated value wearing an authenticated one's type, so the open fails
22
+ * instead. Callers already treat a throw here as ordinary control flow:
23
+ * `decrypt` throws for every epoch but its own.
24
+ */
25
+ export declare function mlsDecryptFramed(handle: GroupHandle, framed: Uint8Array): Promise<{
26
+ payload: Uint8Array;
27
+ senderDID: string;
28
+ }>;
@@ -1,18 +1,33 @@
1
- import { anchorImmutabilityPolicy } from './group-anchor.js';
2
- import { replacer, reviver } from './mls-json.js';
3
- const textEncoder = new TextEncoder();
4
- const textDecoder = new TextDecoder();
5
- export async function mlsEncryptFramed(handle, plaintext) {
6
- const { message, consumed } = await handle.encrypt(plaintext);
7
- const framed = textEncoder.encode(JSON.stringify(message, replacer));
1
+ /**
2
+ * Encrypt an application message for the group. `@kumiai/mls` returns framed MLS
3
+ * wire bytes, so kubun puts those on the wire directly — no JSON envelope of its
4
+ * own, and no retired secret material crossing the boundary.
5
+ */ export async function mlsEncryptFramed(handle, plaintext) {
6
+ return await handle.encrypt(plaintext);
7
+ }
8
+ /**
9
+ * Decrypt a received application message and recover WHO SENT IT.
10
+ *
11
+ * `handle.decrypt` rather than `processMessage`, which returns an application
12
+ * message's plaintext with no sender. The sender is AUTHENTICATED, not claimed:
13
+ * the leaf index rides sender-data encrypted under the epoch's sender-data
14
+ * secret, and the body opens only under the ratchet key derived at that leaf, so
15
+ * a frame naming a leaf it was not sealed at does not open at all.
16
+ *
17
+ * A missing sender is an ERROR, never a filled-in blank. Upstream types
18
+ * `senderDID` optional because the authenticated leaf may hold no parsable
19
+ * credential — "I cannot name the author", never "there is no author". Any
20
+ * placeholder (empty string, a transport-claimed DID) would hand callers an
21
+ * unauthenticated value wearing an authenticated one's type, so the open fails
22
+ * instead. Callers already treat a throw here as ordinary control flow:
23
+ * `decrypt` throws for every epoch but its own.
24
+ */ export async function mlsDecryptFramed(handle, framed) {
25
+ const { payload, senderDID } = await handle.decrypt(framed);
26
+ if (senderDID == null) {
27
+ throw new Error('MLS application message opened at a leaf with no nameable sender');
28
+ }
8
29
  return {
9
- framed,
10
- consumed
30
+ payload,
31
+ senderDID
11
32
  };
12
33
  }
13
- export async function mlsDecryptFramed(handle, framed) {
14
- const message = JSON.parse(textDecoder.decode(framed), reviver);
15
- return await handle.decrypt(message, {
16
- commitPolicy: anchorImmutabilityPolicy
17
- });
18
- }
@@ -19,12 +19,15 @@ import { mlsDecryptFramed, mlsEncryptFramed } from './mls-codec.js';
19
19
  this.#groupID = params.groupID;
20
20
  }
21
21
  encrypt(plaintext) {
22
- return this.#registry.withHandle(this.#groupID, async (handle)=>{
23
- const { framed } = await mlsEncryptFramed(handle, plaintext);
24
- return framed;
25
- });
22
+ return this.#registry.withHandle(this.#groupID, (handle)=>mlsEncryptFramed(handle, plaintext));
26
23
  }
27
- decrypt(ciphertext) {
28
- return this.#registry.withHandle(this.#groupID, (handle)=>mlsDecryptFramed(handle, ciphertext));
24
+ // The `Encryptor` port is bytes-in/bytes-out, so the authenticated sender the
25
+ // open also recovers is discarded here. The sync-transport path this serves
26
+ // carries its own per-entry signed tokens and authorizes on those; it has no
27
+ // seam to receive a frame sender through. Dropping it is deliberate — not an
28
+ // oversight to be routed around by widening the port.
29
+ async decrypt(ciphertext) {
30
+ const { payload } = await this.#registry.withHandle(this.#groupID, (handle)=>mlsDecryptFramed(handle, ciphertext));
31
+ return payload;
29
32
  }
30
33
  }
@@ -1,4 +1,39 @@
1
- import { type GroupHandle } from '@kumiai/mls';
1
+ import { type GroupHandle, type VerifiedLedgerEntry } from '@kumiai/mls';
2
2
  import { type SerializedMLSGroupState } from './mls-state.js';
3
3
  export type MLSGroupHandle = GroupHandle;
4
- export declare function restoreMLSGroupHandle(serialized: SerializedMLSGroupState): Promise<MLSGroupHandle>;
4
+ /**
5
+ * Fetch control-ledger entry bodies a handle does not hold, by content id.
6
+ *
7
+ * A commit's envelope names the entries it enacts by content id, not by body, and
8
+ * the ONLY source for a body a receiver lacks is the commit's own frame, wired in
9
+ * for the duration of applying it. The answer is untrusted: only a token whose
10
+ * digest matches the requested id and whose signature verifies is kept.
11
+ *
12
+ * A handle built without a resolver resolves nothing, deliberately: kubun's store
13
+ * writes its copy AFTER apply, so reading it would answer every miss with an empty
14
+ * list — indistinguishable from a resolver that ran. Passing none instead lets
15
+ * `MissingLedgerEntriesError` reach the lane, which drops the frame as poison.
16
+ */
17
+ export type LedgerEntryResolver = (ids: Array<string>) => Promise<Array<string>>;
18
+ /**
19
+ * Receive the notarized non-`group.role` entries an accepted commit carried, in
20
+ * envelope order. Synchronous by contract, so a sink can only COLLECT: any store
21
+ * write the entries drive must run after `processMessage` returns.
22
+ */
23
+ export type LedgerEntrySink = (entries: Array<VerifiedLedgerEntry>) => void;
24
+ export type RestoreMLSGroupHandleParams = {
25
+ serialized: SerializedMLSGroupState;
26
+ /**
27
+ * Resolves the entry bodies a received commit's envelope names, from the
28
+ * commit's own frame. Supplied only while the restored handle is applying such
29
+ * a commit.
30
+ */
31
+ resolveLedgerEntries?: LedgerEntryResolver;
32
+ /**
33
+ * Collects the notarized entries an accepted commit surfaces on this handle,
34
+ * so the caller can fold them into the projections once `processMessage` has
35
+ * returned.
36
+ */
37
+ onLedgerEntries?: LedgerEntrySink;
38
+ };
39
+ export declare function restoreMLSGroupHandle(params: RestoreMLSGroupHandleParams): Promise<MLSGroupHandle>;
@@ -1,10 +1,18 @@
1
1
  import { restoreGroup } from '@kumiai/mls';
2
2
  import { deserializeMLSGroupState } from './mls-state.js';
3
- export async function restoreMLSGroupHandle(serialized) {
4
- const { state, credential, rootCapability } = deserializeMLSGroupState(serialized);
5
- return restoreGroup({
3
+ export async function restoreMLSGroupHandle(params) {
4
+ const { state, credential, ledgerEntries } = deserializeMLSGroupState(params.serialized);
5
+ return await restoreGroup({
6
6
  state,
7
7
  credential,
8
- rootCapability
8
+ ledgerEntries,
9
+ options: {
10
+ ...params.resolveLedgerEntries != null ? {
11
+ resolveLedgerEntries: params.resolveLedgerEntries
12
+ } : {},
13
+ ...params.onLedgerEntries != null ? {
14
+ onLedgerEntries: params.onLedgerEntries
15
+ } : {}
16
+ }
9
17
  });
10
18
  }
@@ -11,9 +11,7 @@
11
11
  * ordered receive stream.
12
12
  */
13
13
  export declare class DecryptError extends Error {
14
- #private;
15
14
  constructor(message: string, cause: unknown);
16
- get cause(): unknown;
17
15
  }
18
16
  /**
19
17
  * Thrown when a received MLS Commit targets a future epoch the local handle has
@@ -9,14 +9,11 @@
9
9
  * not help, so callers should ack-and-skip rather than wedge the group's
10
10
  * ordered receive stream.
11
11
  */ export class DecryptError extends Error {
12
- #cause;
13
12
  constructor(message, cause){
14
- super(message);
13
+ super(message, {
14
+ cause
15
+ });
15
16
  this.name = 'DecryptError';
16
- this.#cause = cause;
17
- }
18
- get cause() {
19
- return this.#cause;
20
17
  }
21
18
  }
22
19
  /**
@@ -2,13 +2,15 @@ import type { GroupMLSState, InsertGroupMLSState } from '@kubun/store-p2p';
2
2
  import { type ClientState, type GroupHandle, type MemberCredential } from '@kumiai/mls';
3
3
  /**
4
4
  * Serialized form of a GroupHandle for DB persistence.
5
- * The MLS ClientState is opaque binary; the credential is JSON.
5
+ * The MLS ClientState is opaque binary; the credential and the control-ledger
6
+ * tokens are JSON.
6
7
  */
7
8
  export type SerializedMLSGroupState = {
8
9
  mlsState: Uint8Array;
9
10
  credential: string;
10
11
  epoch: number;
11
- rootCapability: string;
12
+ /** The handle's signed ledger tokens, in enactment order, JSON-encoded. */
13
+ ledgerEntries: string;
12
14
  };
13
15
  /**
14
16
  * Serialize a GroupHandle's state for persistence.
@@ -19,7 +21,7 @@ export declare function serializeMLSGroupState(group: GroupHandle): SerializedML
19
21
  export type MLSGroupState = {
20
22
  state: ClientState;
21
23
  credential: MemberCredential;
22
- rootCapability: string;
24
+ ledgerEntries: Array<string>;
23
25
  };
24
26
  /**
25
27
  * Deserialize persisted state back into GroupHandle constructor params.
@@ -9,7 +9,10 @@ import { normalizeCredential } from '../context/types.js';
9
9
  mlsState: encodeClientState(group.state),
10
10
  credential: JSON.stringify(group.credential),
11
11
  epoch: Number(group.epoch),
12
- rootCapability: group.rootCapability
12
+ // The tokens are the ledger's only export form — the verified entries a
13
+ // handle folds cannot be reconstructed from anything else — so persisting
14
+ // them is what lets a restored handle refold the roster it had.
15
+ ledgerEntries: JSON.stringify(group.ledgerTokens)
13
16
  };
14
17
  }
15
18
  /**
@@ -23,9 +26,23 @@ import { normalizeCredential } from '../context/types.js';
23
26
  return {
24
27
  state,
25
28
  credential: JSON.parse(serialized.credential),
26
- rootCapability: serialized.rootCapability
29
+ ledgerEntries: parseLedgerEntries(serialized.ledgerEntries)
27
30
  };
28
31
  }
32
+ /**
33
+ * Ledger tokens are stored as a JSON array of strings. An empty column (a row
34
+ * written before any entry was enacted) parses to an empty ledger, which folds
35
+ * to the anchor-seeded roster — the same state the handle held.
36
+ */ function parseLedgerEntries(value) {
37
+ if (value === '') {
38
+ return [];
39
+ }
40
+ const parsed = JSON.parse(value);
41
+ if (!Array.isArray(parsed)) {
42
+ throw new Error('Could not decode persisted control-ledger tokens');
43
+ }
44
+ return parsed;
45
+ }
29
46
  /**
30
47
  * Map a persisted `GroupMLSState` DB row to the `SerializedMLSGroupState` shape
31
48
  * expected by `BroadcastService.prepareSend` / `processReceived`.
@@ -38,9 +55,15 @@ import { normalizeCredential } from '../context/types.js';
38
55
  mlsState: row.mls_state,
39
56
  credential: normalizeCredential(row.credential),
40
57
  epoch: row.epoch,
41
- rootCapability: row.root_capability
58
+ ledgerEntries: normalizeLedgerEntriesColumn(row.ledger_entries)
42
59
  };
43
60
  }
61
+ /**
62
+ * The ledger-tokens column is TEXT, but `ParseJSONResultsPlugin` may hand back
63
+ * an already-parsed array — the same asymmetry `normalizeCredential` handles.
64
+ */ function normalizeLedgerEntriesColumn(value) {
65
+ return typeof value === 'string' ? value : JSON.stringify(value);
66
+ }
44
67
  /**
45
68
  * Map a `SerializedMLSGroupState` plus scope (group + device) to an
46
69
  * `InsertGroupMLSState` row suitable for `P2PStoreAPI.saveMLSState`.
@@ -51,6 +74,6 @@ import { normalizeCredential } from '../context/types.js';
51
74
  mls_state: serialized.mlsState,
52
75
  credential: serialized.credential,
53
76
  epoch: serialized.epoch,
54
- root_capability: serialized.rootCapability
77
+ ledger_entries: serialized.ledgerEntries
55
78
  };
56
79
  }
@@ -0,0 +1,90 @@
1
+ import type { StoreProvider } from '@kubun/db';
2
+ import { HLC } from '@kubun/hlc';
3
+ import type { Logger } from '@kubun/logger';
4
+ import type { GatheredPeer, LocalPeerAnnouncement, PeerGatherOptions, PresenceReason } from '../types.js';
5
+ import type { GroupBroadcastMessage } from './broadcast.js';
6
+ import type { PeerQueryParam } from './group-protocols.js';
7
+ /** One `peer/query` reply as the gather surface hands it back. */
8
+ type RawReply = {
9
+ senderDID: string;
10
+ value: unknown;
11
+ };
12
+ export type PeerPresenceParams = {
13
+ stores: StoreProvider;
14
+ /** This device's authenticated DID. */
15
+ localDID: string;
16
+ /** Device-wide clock. Every announcement is stamped from it and never by a caller. */
17
+ hlc: HLC;
18
+ /** The epoch stamped onto this device's own row, as the apply path stamps a received one. */
19
+ getGroupEpoch: (groupID: string) => number | undefined;
20
+ /** Fan one broadcast out across every live peer of the group. */
21
+ publish: (groupID: string, message: GroupBroadcastMessage) => Promise<void>;
22
+ /** Gather `peer/query` replies from the group's live peers. */
23
+ query: (groupID: string, param: PeerQueryParam, options: PeerGatherOptions) => Promise<Array<RawReply>>;
24
+ /**
25
+ * Fold one reply into the projection through the SAME apply a broadcast frame
26
+ * takes, so a gathered announcement and a pushed one cannot diverge.
27
+ */
28
+ applyAnnouncement: (groupID: string, message: GroupBroadcastMessage) => Promise<void>;
29
+ /** Every group this device currently belongs to, for a device-wide announce. */
30
+ joinedGroupIDs: () => Array<string>;
31
+ /**
32
+ * Called after this device declares (or redeclares) its profile — the moment
33
+ * it becomes visible, and so the moment a co-member may start dialling it.
34
+ * Whatever answers those dials is stood up here rather than on a timer.
35
+ */
36
+ onProfileSet?: () => void;
37
+ logger?: Logger;
38
+ };
39
+ /**
40
+ * Everything that makes this device visible to its co-members, and everything
41
+ * that asks who else is.
42
+ *
43
+ * A GATHER IS LIVENESS, NEVER MEMBERSHIP. It reports who answered inside a
44
+ * window; it never deletes a row, and a device that says nothing keeps whatever
45
+ * it last said. Only a roster removal deletes.
46
+ */
47
+ export type PeerPresence = {
48
+ /** Record what this device is, then announce it to every group it belongs to. */
49
+ setProfile: (profile: LocalPeerAnnouncement) => Promise<void>;
50
+ getProfile: () => Promise<LocalPeerAnnouncement | null>;
51
+ /**
52
+ * Announce this device's stored profile to one group. Reports whether anything
53
+ * was said — a device with no profile advertises nothing rather than inventing
54
+ * a label for itself.
55
+ */
56
+ announce: (groupID: string) => Promise<boolean>;
57
+ /** Ask who is live. Annotates the projection; never prunes it. */
58
+ gather: (groupID: string, options?: PeerGatherOptions) => Promise<Array<GatheredPeer>>;
59
+ /**
60
+ * Announce, then gather — what "I have just (re)appeared" means, and what every
61
+ * internal trigger runs.
62
+ *
63
+ * A device with no declared profile does NEITHER: presence is opt-in, and a host
64
+ * that never said what its device is has not joined the peer lane. So the whole
65
+ * invariant is "no profile, no peer-lane traffic" — an explicit
66
+ * {@link PeerPresence.gather} still asks, because there the host did.
67
+ */
68
+ refresh: (groupID: string, options?: PeerGatherOptions) => Promise<Array<GatheredPeer>>;
69
+ /** Fire-and-forget {@link refresh}, off the current turn. For an event handler. */
70
+ scheduleRefresh: (groupID: string, reason: PresenceReason) => void;
71
+ /**
72
+ * Fire-and-forget {@link announce}, off the current turn and COALESCED per
73
+ * group. Only the epoch lane uses this, and only it needs to: a device catching
74
+ * up walks many commits back to back, and one announce per commit is a storm
75
+ * where one announce per burst is the same information.
76
+ */
77
+ scheduleAnnounce: (groupID: string, reason: PresenceReason) => void;
78
+ /**
79
+ * Stop scheduling, and WAIT for whatever is already running.
80
+ *
81
+ * Cancelling the pending timers is only half of it: a scheduled task whose
82
+ * timer has already fired is a live store read, and the caller's next step is
83
+ * closing the database. On Postgres a query overlapping the close wedges it
84
+ * permanently, so abandoning that read hangs the shutdown it was meant to
85
+ * finish.
86
+ */
87
+ dispose: () => Promise<void>;
88
+ };
89
+ export declare function createPeerPresence(params: PeerPresenceParams): PeerPresence;
90
+ export {};