@muhkoo/connect 0.8.0-alpha.0 → 0.10.0-alpha.0

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/connect.d.ts CHANGED
@@ -1415,6 +1415,18 @@ interface ShardByteCache {
1415
1415
  get(hash: string): Promise<Uint8Array | null>;
1416
1416
  put(hash: string, bytes: Uint8Array): Promise<void>;
1417
1417
  }
1418
+ /**
1419
+ * A peer block source (the p2p layer's `PeerExchange` satisfies this
1420
+ * structurally). Consulted between the local cache and origin: a block fetched
1421
+ * from a peer is verified by hash before use. Declared here so the storage layer
1422
+ * doesn't import the p2p module (dependency arrow stays p2p → storage).
1423
+ */
1424
+ interface PeerBlockSource {
1425
+ getBlock(hash: string, opts?: {
1426
+ timeoutMs?: number;
1427
+ }): Promise<Uint8Array | null>;
1428
+ announce(hash: string): void;
1429
+ }
1418
1430
  interface ShardClientOptions {
1419
1431
  /** Base URL of the accelerator (e.g. `https://muhkoo-accelerator.workers.dev`). */
1420
1432
  baseUrl: string;
@@ -1446,6 +1458,12 @@ interface ShardClientOptions {
1446
1458
  * keyed by `hash`, so only the hash needs recording.
1447
1459
  */
1448
1460
  deferUpload?: (hash: string) => Promise<void>;
1461
+ /**
1462
+ * Optional peer block source. When present, GETs try peers between the local
1463
+ * cache and origin (peer blocks are verified by hash); PUTs announce the new
1464
+ * block to peers. Best-effort — any failure falls through to origin.
1465
+ */
1466
+ peers?: PeerBlockSource;
1449
1467
  }
1450
1468
  /**
1451
1469
  * Compute the lowercase hex SHA-256 of `bytes`. Available via WebCrypto in
@@ -1458,6 +1476,7 @@ declare class ShardClient {
1458
1476
  private readonly fetchFn;
1459
1477
  private readonly cache?;
1460
1478
  private readonly deferUpload?;
1479
+ private readonly peers?;
1461
1480
  constructor(opts: ShardClientOptions);
1462
1481
  /** Build the URL for a given shard hash — exposed for callers that want to log or pre-resolve. */
1463
1482
  urlForHash(hash: string): string;
@@ -1475,6 +1494,9 @@ declare class ShardClient {
1475
1494
  * `null` for 404 so callers can distinguish "missing" from "errored".
1476
1495
  */
1477
1496
  getShard(hash: string): Promise<Uint8Array | null>;
1497
+ /** Fetch a shard from origin. Returns `null` for 404 (or offline when a
1498
+ * cache is present, so RS recovery can proceed); throws otherwise. */
1499
+ private fetchFromOrigin;
1478
1500
  /**
1479
1501
  * Check whether a shard exists. Lets the caller skip a PUT they'd otherwise
1480
1502
  * dedup-rewrite. The server can implement this as a HEAD or return a small
@@ -2492,6 +2514,21 @@ declare class ZkAuth {
2492
2514
  * to re-enable encryption/messaging without a full re-login.
2493
2515
  */
2494
2516
  restore(): Promise<AuthUser | null>;
2517
+ /**
2518
+ * Boot helper — call this once on app start. Restores the session AND, when a
2519
+ * passkey is enrolled, silently unlocks the identity via WebAuthn (recovering
2520
+ * the master seed so the ratchet keypair rehydrates and `client.kv` works).
2521
+ * The seed is never persisted, so a passkey (or password) is the only way to
2522
+ * unlock after a reload — this keeps that off the app's plate.
2523
+ *
2524
+ * Returns `{ user, unlocked }`. When `unlocked` is false (no passkey, the
2525
+ * user cancelled, or the browser required a gesture), prompt for a password
2526
+ * and call {@link unlock}. Apps need no key-management scaffolding beyond this.
2527
+ */
2528
+ resume(): Promise<{
2529
+ user: AuthUser | null;
2530
+ unlocked: boolean;
2531
+ }>;
2495
2532
  /**
2496
2533
  * Re-derive identity material for an already-authenticated (restored)
2497
2534
  * session, so the client can decrypt storage and run the ratchet. Verifies
@@ -3704,11 +3741,29 @@ declare class SpaceKeyring implements EpochKeyProvider {
3704
3741
  private readonly policy;
3705
3742
  /** Cached member directory: memberId → imported ECDSA verify key. */
3706
3743
  private readonly memberEcdsa;
3744
+ /** Coalesce concurrent roster refreshes (the parallel history/backlog decode
3745
+ * would otherwise fire one `roster` fetch PER message — a thundering herd). */
3746
+ private dirInFlight;
3747
+ private dirRefreshedAt;
3748
+ /** Min gap between non-forced roster refreshes. A miss that persists within
3749
+ * this window won't re-fetch (the roster hasn't changed that fast); genuine
3750
+ * membership changes use `{ force: true }` (join requests / history loads). */
3751
+ private static readonly DIR_COOLDOWN_MS;
3707
3752
  constructor(deps: SpaceKeyringDeps);
3708
3753
  /** The cached ECDSA verify key for a member, if known. */
3709
3754
  ecdsaKeyFor(memberId: string): CryptoKey | undefined;
3710
- /** Refresh the member directory (memberId → ECDSA key) from the roster. */
3711
- refreshDirectory(): Promise<void>;
3755
+ /**
3756
+ * Refresh the member directory (memberId → ECDSA key) from the roster.
3757
+ * Coalesces concurrent callers into ONE fetch, and skips a fetch when one
3758
+ * completed within {@link DIR_COOLDOWN_MS} (unless `force`). Without this, a
3759
+ * page of N messages decoded in parallel — each missing the cold directory —
3760
+ * fires N simultaneous `roster` requests that queue behind the browser's
3761
+ * connection limit and take many seconds.
3762
+ */
3763
+ refreshDirectory(opts?: {
3764
+ force?: boolean;
3765
+ }): Promise<void>;
3766
+ private doRefreshDirectory;
3712
3767
  currentEpoch(): number;
3713
3768
  keyForEpoch(epoch: number): Uint8Array | undefined;
3714
3769
  hasAnyKey(): boolean;
@@ -3729,6 +3784,13 @@ declare class SpaceKeyring implements EpochKeyProvider {
3729
3784
  * epoch keys obtained. Safe to call repeatedly (idempotent).
3730
3785
  */
3731
3786
  pullKeys(): Promise<number>;
3787
+ /**
3788
+ * Background freshness check for the cache-hit fast path: confirm we hold the
3789
+ * current epoch (picks up a rotation / our removal) WITHOUT the full newcomer
3790
+ * admit handshake. Cheap + idempotent; never blocks first paint. Returns the
3791
+ * number of newly-obtained epoch keys (0 = already current).
3792
+ */
3793
+ reconcile(): Promise<number>;
3732
3794
  /**
3733
3795
  * Wrap our group key(s) for a newcomer and post the blobs. In a `static`
3734
3796
  * space the newcomer receives every epoch we hold (full history); in a
@@ -3775,6 +3837,19 @@ declare class SpaceKeyring implements EpochKeyProvider {
3775
3837
  */
3776
3838
 
3777
3839
  type Listener = (e: CustomEvent) => void;
3840
+ /**
3841
+ * A P2P swarm attached to a Space (the p2p layer's `PeerNetwork` satisfies this
3842
+ * structurally — Space never imports the p2p module). `exchange` is consulted by
3843
+ * the Space's file `ShardClient`; `start` runs peer discovery on connect; `close`
3844
+ * tears the mesh down.
3845
+ */
3846
+ interface AttachedPeerNetwork {
3847
+ readonly exchange: PeerBlockSource;
3848
+ start(): void;
3849
+ close(): void;
3850
+ gossip(data: Uint8Array): void;
3851
+ onGossip(cb: (from: string, data: Uint8Array) => void): () => void;
3852
+ }
3778
3853
  interface SpaceFileMetadata {
3779
3854
  name: string;
3780
3855
  type: string;
@@ -3914,6 +3989,8 @@ declare class Space {
3914
3989
  private readonly events;
3915
3990
  private rawWired;
3916
3991
  private catchUpUnsub;
3992
+ /** Optional P2P swarm for this Space (block exchange among members). */
3993
+ private peerNet;
3917
3994
  constructor(deps: SpaceDeps);
3918
3995
  /** The encoded space id (same as the transport name for fan-out spaces). */
3919
3996
  get id(): string;
@@ -3922,6 +3999,19 @@ declare class Space {
3922
3999
  /** Open the space websocket (resolves the ticket + identity lazily). */
3923
4000
  connect(): Promise<void>;
3924
4001
  disconnect(): void;
4002
+ /** Attach a P2P swarm so this Space's file reads/writes can use peers. */
4003
+ attachPeerNetwork(net: AttachedPeerNetwork): void;
4004
+ /**
4005
+ * Broadcast an app payload directly to connected Space peers over P2P,
4006
+ * **bypassing the server** — for CRDT ops / high-frequency state (live
4007
+ * cursors, presence, collaborative edits) you don't want to push through the
4008
+ * relay. No-op when no P2P mesh is active (requires `client` `p2p`).
4009
+ * Unlike {@link sendEphemeral} (server-relayed, metered, size-limited), this
4010
+ * is peer-direct and fragments large payloads.
4011
+ */
4012
+ gossipToPeers(data: Uint8Array): void;
4013
+ /** Subscribe to peer gossip ({@link gossipToPeers}). Returns an unsubscribe fn. */
4014
+ onPeerGossip(handler: (from: string, data: Uint8Array) => void): () => void;
3925
4015
  /** Subscribe to a raw channel event. Buffered until `connect()` if early. */
3926
4016
  on(event: string, handler: Listener): void;
3927
4017
  off(event: string, handler: Listener): void;
@@ -3982,7 +4072,7 @@ declare class Space {
3982
4072
  * indicators, presence, or live cursors are built on top of this. No-op if
3983
4073
  * the space isn't connected (ephemeral signals aren't worth queuing).
3984
4074
  */
3985
- sendEphemeral(subject: string, data: unknown): void;
4075
+ sendEphemeral(subject: string, data: unknown, to?: string): void;
3986
4076
  /** Subscribe to inbound ephemeral signals. `from` is the authenticated sender. */
3987
4077
  onEphemeral(handler: (e: EphemeralEvent) => void): () => void;
3988
4078
  /** Subscribe to inbound join requests (for a key-holder to admit). */
@@ -4160,6 +4250,53 @@ declare class MessageNamespace {
4160
4250
  private myId;
4161
4251
  }
4162
4252
 
4253
+ /**
4254
+ * `ChatKeyVault` — SDK-owned lifecycle for a member's long-lived ratchet/space
4255
+ * keypair (the connect `KeyStore` entry keyed by the member id / username).
4256
+ *
4257
+ * Historically each app hand-rolled this (see the web app's old
4258
+ * `personal/spaceLoader.ts`): generate a keypair at register, seed-wrap it, and
4259
+ * PUT it to the personal space; on login GET + unwrap + hydrate the `KeyStore`.
4260
+ * That scaffolding now lives here so ANY app gets a **stable** member keypair
4261
+ * across reloads for free — without which the member re-admits to every space on
4262
+ * every load and the group-key cache can never round-trip.
4263
+ *
4264
+ * Security invariant: the wrapping secret is the **master seed** (not the
4265
+ * password), so the blob survives password changes and unlocks under a passkey.
4266
+ * The seed is only ever held in memory (never persisted), so this vault can only
4267
+ * provision/rehydrate while the client is unlocked.
4268
+ */
4269
+
4270
+ /** Opaque personal-space blob store (a `PersonalSpaceClient` or a kv-backed shim). */
4271
+ interface ChatKeyStore {
4272
+ get(key: string): Promise<WrappedPayload | null>;
4273
+ put(key: string, value: WrappedPayload): Promise<void>;
4274
+ }
4275
+ declare class ChatKeyVault {
4276
+ private readonly store;
4277
+ constructor(store: ChatKeyStore);
4278
+ /**
4279
+ * Register-time: ensure a keypair exists for `memberId`, seed-wrap the
4280
+ * dehydrated form, and persist it to the personal space. Idempotent — reuses
4281
+ * an existing in-tab keypair rather than minting a second one.
4282
+ */
4283
+ provision(memberId: string, seedSecret: string): Promise<void>;
4284
+ /**
4285
+ * Sign-in / unlock time: fetch the wrapped blob, unwrap with the seed, and
4286
+ * hydrate the `KeyStore` — restoring the SAME keypair the member had before.
4287
+ * Returns `true` when a keypair is now in hand (already present or hydrated),
4288
+ * `false` when there's no blob yet (a brand-new member → caller provisions).
4289
+ * Throws only on a genuine unwrap failure (wrong secret / tampered blob).
4290
+ */
4291
+ rehydrate(memberId: string, seedSecret: string): Promise<boolean>;
4292
+ /**
4293
+ * Ensure a stable keypair is in hand: rehydrate from the vault if a blob
4294
+ * exists, otherwise mint one and persist it (so it's stable next session).
4295
+ * Used at register and lazily when a space is first opened.
4296
+ */
4297
+ ensure(memberId: string, seedSecret: string): Promise<void>;
4298
+ }
4299
+
4163
4300
  /**
4164
4301
  * `client.space` — the fan-out group-encryption surface.
4165
4302
  *
@@ -4195,6 +4332,18 @@ interface SpaceNamespaceDeps {
4195
4332
  cache?: SpaceKeyCache;
4196
4333
  /** Offline manager — enables message caching + offline send replay. */
4197
4334
  offline?: OfflineManager;
4335
+ /** SDK-owned ratchet-keypair vault — gives a STABLE member keypair across
4336
+ * reloads (rehydrate-or-provision) instead of a fresh one each session. */
4337
+ chatKeys?: ChatKeyVault;
4338
+ /** P2P swarm config — enables peer block exchange among Space members. */
4339
+ p2p?: {
4340
+ enabled?: boolean;
4341
+ /** `() => new Worker(new URL(...))` to run the block engine off-thread. */
4342
+ workerFactory?: () => Worker;
4343
+ iceServers?: RTCIceServer[];
4344
+ maxPeers?: number;
4345
+ debug?: boolean;
4346
+ };
4198
4347
  }
4199
4348
  declare class SpaceNamespace {
4200
4349
  private readonly deps;
@@ -4661,6 +4810,20 @@ interface ClientOptions {
4661
4810
  /** Soft cap on durable-queue bytes before degrading to online-only. */
4662
4811
  maxQueueBytes?: number;
4663
4812
  };
4813
+ /**
4814
+ * Peer-to-peer block exchange (`client.space` files) among Space members,
4815
+ * over WebRTC signaled on the Space relay. Best-effort — falls back to
4816
+ * origin. **Opt-in** (browser only). Pass `workerFactory` to run the block
4817
+ * engine off the main thread.
4818
+ */
4819
+ p2p?: {
4820
+ enabled?: boolean;
4821
+ workerFactory?: () => Worker;
4822
+ iceServers?: RTCIceServer[];
4823
+ maxPeers?: number;
4824
+ /** Log P2P mesh + block-exchange activity to the console (staging/dev). */
4825
+ debug?: boolean;
4826
+ };
4664
4827
  }
4665
4828
  declare class Client {
4666
4829
  readonly baseUrl: string;
@@ -5158,6 +5321,386 @@ declare function writeBeatsTombstone(writeHlc: string, tombstoneHlc: string | nu
5158
5321
  */
5159
5322
  declare function deleteBeatsWrite(deleteHlc: string, writeHlc: string | null): boolean;
5160
5323
 
5324
+ /**
5325
+ * `PeerExchange` — the thin façade the storage layer consults to try fetching a
5326
+ * block from peers before falling back to origin. It satisfies the structural
5327
+ * `PeerBlockSource` interface that {@link ../storage/transport/ShardClient}
5328
+ * declares (so storage never imports the p2p module — the dependency arrow stays
5329
+ * one-way, p2p → storage).
5330
+ *
5331
+ * It delegates to an {@link EngineHandle} — either an in-process {@link
5332
+ * ./worker/blockEngine.BlockEngine} or the Web Worker proxy — so the same façade
5333
+ * works whether the block engine runs on the main thread or off it.
5334
+ */
5335
+
5336
+ /** The subset of a block engine (in-process or worker-proxied) PeerExchange drives. */
5337
+ interface EngineHandle {
5338
+ want(hash: string, timeoutMs: number): Promise<Uint8Array | null>;
5339
+ announce(hash: string): void;
5340
+ }
5341
+ interface PeerExchangeOptions {
5342
+ /** How long to wait for a peer before giving up to origin. Default 2500ms. */
5343
+ timeoutMs?: number;
5344
+ /** Log peer-served vs origin-fallback per block to the console. */
5345
+ debug?: boolean;
5346
+ }
5347
+ declare class PeerExchange implements PeerBlockSource {
5348
+ private readonly engine;
5349
+ private readonly timeoutMs;
5350
+ private readonly debug;
5351
+ constructor(engine: EngineHandle, opts?: PeerExchangeOptions);
5352
+ getBlock(hash: string, opts?: {
5353
+ timeoutMs?: number;
5354
+ }): Promise<Uint8Array | null>;
5355
+ announce(hash: string): void;
5356
+ }
5357
+
5358
+ /**
5359
+ * `SpaceSignaler` — WebRTC signaling carried over the Space's existing ephemeral
5360
+ * relay. No new server channel: `Space.sendEphemeral`/`onEphemeral` already
5361
+ * blind-relay `pub` frames stamped with the authenticated sender, so SDP
5362
+ * offers/answers and ICE candidates ride a reserved subject (`__p2p__`) scoped
5363
+ * to Space membership. The sender (`from`) is server-stamped and can't be
5364
+ * spoofed; the payload is opaque to the server.
5365
+ *
5366
+ * Envelopes carry a `to` (a member id, or `"*"` for discovery) so the
5367
+ * accelerator can optionally unicast peer-pair signaling instead of broadcasting
5368
+ * (a small, backward-compatible `pub` enhancement); the client filters by `to`
5369
+ * regardless.
5370
+ */
5371
+ declare const P2P_SUBJECT = "__p2p__";
5372
+ type SignalKind = "hello" | "bye" | "offer" | "answer" | "ice";
5373
+ interface SignalEnvelope {
5374
+ /** Target member id, or `"*"` to broadcast (discovery / departure). */
5375
+ to: string;
5376
+ kind: SignalKind;
5377
+ data?: unknown;
5378
+ }
5379
+ /** The slice of {@link ../../spaces/Space.Space} the signaler needs. */
5380
+ interface SignalingSpace {
5381
+ sendEphemeral(subject: string, data: unknown, to?: string): void;
5382
+ onEphemeral(handler: (e: {
5383
+ from: string;
5384
+ subject: string;
5385
+ data: unknown;
5386
+ }) => void): () => void;
5387
+ }
5388
+ declare class SpaceSignaler {
5389
+ private readonly space;
5390
+ private readonly myId;
5391
+ constructor(space: SignalingSpace, myId: string);
5392
+ /** Send a signal to one peer (or `"*"`). */
5393
+ send(to: string, kind: SignalKind, data?: unknown): void;
5394
+ /** Announce/withdraw P2P availability to all Space members. */
5395
+ broadcast(kind: SignalKind, data?: unknown): void;
5396
+ /**
5397
+ * Subscribe to inbound signals addressed to us (or broadcast). Skips our own
5398
+ * echoes. Returns an unsubscribe function.
5399
+ */
5400
+ onSignal(cb: (from: string, kind: SignalKind, data: unknown) => void): () => void;
5401
+ }
5402
+
5403
+ /**
5404
+ * `PeerTransport` — the pluggable wire under the block engine. It moves opaque
5405
+ * frames between Space peers; it knows nothing about blocks, hashes, or the
5406
+ * protocol. {@link ./WebRtcTransport} implements it over RTCDataChannels today;
5407
+ * a future libp2p/Helia transport can implement the same interface without the
5408
+ * block engine or `ShardClient` changing.
5409
+ *
5410
+ * Implementations are responsible for fragmenting/ reassembling frames larger
5411
+ * than the underlying channel's max message size (a 4 MiB shard won't fit in one
5412
+ * `RTCDataChannel.send`); the engine always deals in whole frames.
5413
+ */
5414
+ type PeerId = string;
5415
+ interface PeerTransport {
5416
+ /** Currently connected peer ids. */
5417
+ peers(): PeerId[];
5418
+ /** Dial a discovered peer (optional — some transports connect implicitly). */
5419
+ connectTo?(peer: PeerId): void;
5420
+ /** Send a whole frame to one peer on `channel` (fragmented internally if needed). */
5421
+ send(peer: PeerId, data: Uint8Array, channel?: number): void;
5422
+ /** Send a whole frame to every connected peer on `channel`. */
5423
+ broadcast(data: Uint8Array, channel?: number): void;
5424
+ /** Subscribe to inbound (reassembled) frames. Returns an unsubscribe fn. */
5425
+ onMessage(cb: (peer: PeerId, data: Uint8Array, channel: number) => void): () => void;
5426
+ /** Subscribe to the connected-peer set changing. Returns an unsubscribe fn. */
5427
+ onPeerChange(cb: (peers: PeerId[]) => void): () => void;
5428
+ /** Tear down all connections. */
5429
+ close(): void;
5430
+ }
5431
+
5432
+ /**
5433
+ * `BlockEngine` — the bitswap-lite want/have ledger. Host-agnostic: it speaks
5434
+ * only to a {@link BlockStore} (get/put/has) and an `out` sink that ships
5435
+ * encoded frames to a peer (or `"*"` to broadcast). The host wires `out` and
5436
+ * `handleFrame` to a real transport — on the main thread or inside the Web
5437
+ * Worker (see {@link ./engineClient}) — so this class is pure and unit-testable.
5438
+ *
5439
+ * It never sees keys: blocks are AES-GCM ciphertext, addressed by SHA-256. The
5440
+ * one trust check is integrity — an inbound BLOCK is re-hashed and dropped on
5441
+ * mismatch, so a malicious peer can't poison the store (the requester just
5442
+ * falls back to origin).
5443
+ */
5444
+ interface BlockStore {
5445
+ get(hash: string): Promise<Uint8Array | null>;
5446
+ put(hash: string, bytes: Uint8Array): Promise<void>;
5447
+ has(hash: string): Promise<boolean>;
5448
+ }
5449
+ /** Ship an encoded frame to one peer, or `"*"` for every connected peer. */
5450
+ type OutSink = (target: string | "*", frame: Uint8Array) => void;
5451
+ /** Lowercase hex SHA-256 of `bytes` — injected so tests can stub it. */
5452
+ type Hasher = (bytes: Uint8Array) => Promise<string>;
5453
+ declare class BlockEngine {
5454
+ private readonly store;
5455
+ private readonly out;
5456
+ private readonly hash;
5457
+ private readonly wants;
5458
+ constructor(store: BlockStore, out: OutSink, hash: Hasher);
5459
+ /**
5460
+ * Ask the swarm for a block. Resolves with the verified bytes, or `null` on
5461
+ * timeout (caller then falls back to origin). Returns immediately if we
5462
+ * already hold it; concurrent wants for the same hash share one request.
5463
+ */
5464
+ want(hash: string, timeoutMs: number): Promise<Uint8Array | null>;
5465
+ /** Tell the swarm we now hold `hash` (so peers wanting it can pull from us). */
5466
+ announce(hash: string): void;
5467
+ /** Handle one inbound frame from `peer`. */
5468
+ handleFrame(peer: string, frame: Uint8Array): Promise<void>;
5469
+ /** Drop all pending waits (resolve null) — on teardown. */
5470
+ close(): void;
5471
+ }
5472
+
5473
+ /**
5474
+ * `PeerNetwork` — the per-Space orchestrator that ties the pieces together on
5475
+ * the main thread:
5476
+ *
5477
+ * transport (WebRTC) ⇄ engine host (Worker or in-process) → PeerExchange
5478
+ * ▲ │
5479
+ * └────────────── signaling over the Space relay ─────────────┘
5480
+ *
5481
+ * It owns one {@link ./transport/WebRtcTransport} + one {@link
5482
+ * ./worker/engineHost.EngineHost}, wires inbound frames to the engine and the
5483
+ * engine's outbound frames back to the transport, runs peer discovery over the
5484
+ * Space's ephemeral channel, and exposes a {@link ./PeerExchange.PeerExchange}
5485
+ * for `ShardClient` to consult. Scope is one Space (private swarm).
5486
+ *
5487
+ * `engineHost` defaults to in-process; pass `workerFactory` to run the block
5488
+ * engine off the main thread (the consumer's bundler resolves the worker URL).
5489
+ */
5490
+
5491
+ interface PeerNetworkOptions {
5492
+ /** The Space (used for signaling over its ephemeral relay). */
5493
+ space: SignalingSpace;
5494
+ /** Our authenticated member id (the Space's `myId`). */
5495
+ myId: string;
5496
+ /** Blockstore for the in-process engine. Defaults to a `ShardCache`. */
5497
+ store?: BlockStore;
5498
+ /** Run the block engine in a Worker — `() => new Worker(new URL(...))`. */
5499
+ workerFactory?: () => Worker;
5500
+ iceServers?: RTCIceServer[];
5501
+ maxPeers?: number;
5502
+ /** Peer fetch timeout passed to PeerExchange. */
5503
+ timeoutMs?: number;
5504
+ /** Inject a transport (tests / a future libp2p transport). Default WebRTC. */
5505
+ transport?: PeerTransport;
5506
+ /** Log mesh + block-exchange activity to the console (use on staging/dev). */
5507
+ debug?: boolean;
5508
+ }
5509
+ declare class PeerNetwork {
5510
+ /** Pass this to a `ShardClient` as `peers`. */
5511
+ readonly exchange: PeerExchange;
5512
+ private readonly transport;
5513
+ private readonly host;
5514
+ private readonly signaler;
5515
+ private readonly offs;
5516
+ private readonly gossipCbs;
5517
+ private closed;
5518
+ constructor(opts: PeerNetworkOptions);
5519
+ /**
5520
+ * Begin peer discovery — broadcast a hello so existing members dial us and
5521
+ * we learn them. Call once the signaling channel is live (the Space fires
5522
+ * this on CONNECTED); safe to call again on reconnect.
5523
+ */
5524
+ start(): void;
5525
+ /**
5526
+ * Broadcast an app payload to all connected peers over the gossip channel —
5527
+ * a direct, server-bypassing data path for CRDT ops / high-frequency state
5528
+ * (cursors, presence, collaborative edits). Single-hop (no re-forwarding).
5529
+ */
5530
+ gossip(data: Uint8Array): void;
5531
+ /** Subscribe to gossip from peers. Returns an unsubscribe function. */
5532
+ onGossip(cb: (from: string, data: Uint8Array) => void): () => void;
5533
+ /** Connected peer count (open data channels). */
5534
+ peerCount(): number;
5535
+ close(): void;
5536
+ }
5537
+
5538
+ /**
5539
+ * Whether this environment can do WebRTC peering. `RTCPeerConnection` is
5540
+ * Window-only (absent in Node/Workers/SSR), so this gates whether a
5541
+ * {@link ./PeerNetwork} can be built at all.
5542
+ */
5543
+ declare function isP2pCapable(): boolean;
5544
+
5545
+ /**
5546
+ * `WebRtcTransport` — a {@link ./PeerTransport.PeerTransport} over RTCDataChannels,
5547
+ * signaled by {@link ../signaling/SpaceSignaler}. Runs on the **main thread**
5548
+ * (`RTCPeerConnection` is Window-only — not available in Workers), so the block
5549
+ * engine lives in a Worker and this only shuttles bytes.
5550
+ *
5551
+ * Connection setup uses a deterministic initiator (the peer with the smaller id
5552
+ * dials) to avoid offer/answer glare. STUN handles NAT traversal; there is no
5553
+ * TURN — an unreachable peer simply never connects and the caller falls back to
5554
+ * origin (P2P is best-effort).
5555
+ *
5556
+ * Frames (up to a 4 MiB shard) exceed an RTCDataChannel's max message size, so
5557
+ * each frame is fragmented into `CHUNK`-sized pieces with a 12-byte header
5558
+ * `[msgId u32][index u32][total u32]` and reassembled per peer.
5559
+ */
5560
+
5561
+ interface WebRtcTransportOptions {
5562
+ iceServers?: RTCIceServer[];
5563
+ /** Cap on simultaneous peer connections (mesh size). Default 8. */
5564
+ maxPeers?: number;
5565
+ }
5566
+ declare class WebRtcTransport implements PeerTransport {
5567
+ private readonly signaler;
5568
+ private readonly myId;
5569
+ private readonly conns;
5570
+ private readonly msgCbs;
5571
+ private readonly peerCbs;
5572
+ private readonly offSignal;
5573
+ private readonly iceServers;
5574
+ private readonly maxPeers;
5575
+ private nextMsgId;
5576
+ constructor(signaler: SpaceSignaler, myId: PeerId, opts?: WebRtcTransportOptions);
5577
+ peers(): PeerId[];
5578
+ /** Dial a peer (no-op unless we're the deterministic initiator for the pair). */
5579
+ connectTo(peerId: PeerId): void;
5580
+ send(peer: PeerId, data: Uint8Array, channel?: number): void;
5581
+ broadcast(data: Uint8Array, channel?: number): void;
5582
+ onMessage(cb: (peer: PeerId, data: Uint8Array, channel: number) => void): () => void;
5583
+ onPeerChange(cb: (peers: PeerId[]) => void): () => void;
5584
+ close(): void;
5585
+ private newPc;
5586
+ private initiate;
5587
+ private bindChannel;
5588
+ private onSignal;
5589
+ private drop;
5590
+ private firePeerChange;
5591
+ private sendFragmented;
5592
+ private drain;
5593
+ private onChunk;
5594
+ }
5595
+
5596
+ /**
5597
+ * `EngineHost` — the boundary {@link ../PeerNetwork} wires a transport to. It
5598
+ * abstracts *where* the {@link ./blockEngine.BlockEngine} runs:
5599
+ * - {@link LocalEngineHost} runs it on the calling thread (tests, or a
5600
+ * fallback when the Web Worker can't be built by a consumer's bundler);
5601
+ * - the Worker proxy ({@link ./engineClient}) runs it off-thread.
5602
+ *
5603
+ * Either way the host exposes the same surface: drive `want`/`announce`, feed it
5604
+ * inbound frames via `handleFrame`, and receive its outbound frames via
5605
+ * `onOutbound` (which the network forwards to the transport).
5606
+ */
5607
+
5608
+ interface EngineHost {
5609
+ want(hash: string, timeoutMs: number): Promise<Uint8Array | null>;
5610
+ announce(hash: string): void;
5611
+ /** Feed an inbound frame received from `peer` on the transport. */
5612
+ handleFrame(peer: string, frame: Uint8Array): void;
5613
+ /** Receive frames the engine wants to send (`"*"` = broadcast). */
5614
+ onOutbound(cb: (target: string, frame: Uint8Array) => void): () => void;
5615
+ close(): void;
5616
+ }
5617
+ /** Runs the engine in-process (main thread or test). */
5618
+ declare class LocalEngineHost implements EngineHost {
5619
+ private readonly engine;
5620
+ private readonly cbs;
5621
+ constructor(store: BlockStore, hasher: Hasher);
5622
+ want(hash: string, timeoutMs: number): Promise<Uint8Array | null>;
5623
+ announce(hash: string): void;
5624
+ handleFrame(peer: string, frame: Uint8Array): void;
5625
+ onOutbound(cb: (target: string, frame: Uint8Array) => void): () => void;
5626
+ close(): void;
5627
+ }
5628
+
5629
+ /**
5630
+ * `WorkerEngineHost` — the main-thread proxy to a {@link ./blockEngine.BlockEngine}
5631
+ * running inside a dedicated Web Worker (so hashing, blockstore I/O, and the
5632
+ * protocol codec stay off the main thread). It speaks the small message protocol
5633
+ * in {@link ./blockEngine.worker} and satisfies {@link ./engineHost.EngineHost},
5634
+ * so {@link ../PeerNetwork} treats it identically to the in-process host.
5635
+ *
5636
+ * Block bytes cross the worker boundary as **transferable** ArrayBuffers
5637
+ * (zero-copy). Only encrypted blocks ever cross — keys never enter the worker.
5638
+ */
5639
+
5640
+ declare class WorkerEngineHost implements EngineHost {
5641
+ private readonly worker;
5642
+ private readonly wants;
5643
+ private readonly cbs;
5644
+ private nextId;
5645
+ constructor(worker: Worker);
5646
+ want(hash: string, timeoutMs: number): Promise<Uint8Array | null>;
5647
+ announce(hash: string): void;
5648
+ handleFrame(peer: string, frame: Uint8Array): void;
5649
+ onOutbound(cb: (target: string, frame: Uint8Array) => void): () => void;
5650
+ close(): void;
5651
+ }
5652
+
5653
+ /**
5654
+ * Bitswap-lite wire protocol for peer block exchange. Four frame types over a
5655
+ * {@link ../transport/PeerTransport} data channel:
5656
+ *
5657
+ * WANT(hash) — "does anyone have this block?" / "send it to me"
5658
+ * HAVE(hash) — "I hold this block" (announce / reply to a WANT)
5659
+ * BLOCK(hash, bytes) — the block itself (verified by hash on receipt)
5660
+ * CANCEL(hash) — "stop, I got it elsewhere"
5661
+ *
5662
+ * Frames are compact binary so a 4 MiB shard isn't base64-bloated through JSON:
5663
+ *
5664
+ * byte 0 : type (0 WANT | 1 HAVE | 2 BLOCK | 3 CANCEL)
5665
+ * bytes 1..33 : 32-byte raw SHA-256 of the block (our shard hash, hex-decoded)
5666
+ * bytes 33.. : payload (BLOCK only)
5667
+ *
5668
+ * Blocks are AES-GCM ciphertext, so a frame carries nothing readable; the hash
5669
+ * is the capability check (the receiver re-hashes the payload and drops a
5670
+ * mismatch). See {@link ./blockEngine}.
5671
+ */
5672
+ declare enum FrameType {
5673
+ WANT = 0,
5674
+ HAVE = 1,
5675
+ BLOCK = 2,
5676
+ CANCEL = 3
5677
+ }
5678
+ interface DecodedFrame {
5679
+ type: FrameType;
5680
+ /** Lowercase hex SHA-256 (matches `shardHash`). */
5681
+ hash: string;
5682
+ /** Present only for {@link FrameType.BLOCK}. */
5683
+ payload?: Uint8Array;
5684
+ }
5685
+ declare function encodeWant(hash: string): Uint8Array;
5686
+ declare function encodeHave(hash: string): Uint8Array;
5687
+ declare function encodeCancel(hash: string): Uint8Array;
5688
+ declare function encodeBlock(hash: string, payload: Uint8Array): Uint8Array;
5689
+ declare function decode(frame: Uint8Array): DecodedFrame;
5690
+
5691
+ type protocol_DecodedFrame = DecodedFrame;
5692
+ type protocol_FrameType = FrameType;
5693
+ declare const protocol_FrameType: typeof FrameType;
5694
+ declare const protocol_decode: typeof decode;
5695
+ declare const protocol_encodeBlock: typeof encodeBlock;
5696
+ declare const protocol_encodeCancel: typeof encodeCancel;
5697
+ declare const protocol_encodeHave: typeof encodeHave;
5698
+ declare const protocol_encodeWant: typeof encodeWant;
5699
+ declare namespace protocol {
5700
+ export { protocol_FrameType as FrameType, protocol_decode as decode, protocol_encodeBlock as encodeBlock, protocol_encodeCancel as encodeCancel, protocol_encodeHave as encodeHave, protocol_encodeWant as encodeWant };
5701
+ export type { protocol_DecodedFrame as DecodedFrame };
5702
+ }
5703
+
5161
5704
  /**
5162
5705
  * KeyringClient — HTTP implementation of {@link KeyringTransport} against the
5163
5706
  * accelerator's per-space keyring endpoints. All blobs are opaque to the
@@ -5218,5 +5761,5 @@ declare class KeyringClient implements KeyringTransport {
5218
5761
  fetchMetadata(): Promise<SpaceMetadata | null>;
5219
5762
  }
5220
5763
 
5221
- export { AES_GCM_IV_BYTES, AES_GCM_KEY_BYTES, AES_GCM_TAG_BYTES, AgentsNamespace, AuthClient, AuthNamespace, AuthPublicInput, Authenticator, BroadcastChannel, BroadcastChannelEvents, ChannelExistsError, ChannelNotFoundError, ChunkCipher, Client, ConnectivityManager, DEFAULT_AUTH_BASE_URL, DEFAULT_BASE_URL, DEFAULT_FN_HOST_SUFFIX, DbCache, DoubleRatchet, DoubleRatchetCipher, DoubleRatchetManager, EncryptedSession, EventCore, EventCoreEvents, Field, Field as FieldElement, FileStorage, FunctionsNamespace, HLC, HashKnowledge, HttpClient, HttpError, IndexedDbStore, KEEPER_MEMBER_ID, KeyStore, KeyringClient, KvCache, KvNamespace, LocalStorageSessionStore, MemorySessionStore, Message, Message as MessageDefault, MessageNamespace, MuhkooAgent, MuhkooDB, MuhkooFunction, MuhkooSpace, NoopStore, OfflineLockedError, OfflineManager, OutboundQueue, PBKDF2_DEFAULT_ITERATIONS, PREIMAGE_POK_VERIFICATION_KEY, Packet, PersonalSpaceClient, Poseidon, PreimagePoK, ReedSolomonCodec, Space as Room, SerializeMessage, SessionState, ShardCache, ShardClient, SharedSpaceClient, SnapshotNamespace, Space, SpaceCache, SpaceKeyring, SpaceNamespace, SpacePacketCipher, StorageNamespace, SyncEngine, VaultUnavailableError, WSTransport, ZERO_HLC, ZkAuth, buildCommitment, compareHlc, compilePrograms, decodeFromHex, decorators, decryptAesGcm, defaultCircuitUrls, defaultSessionStore, deleteBeatsWrite, deleteRegister, deriveAesKeyFromPassphrase, deriveBitsHkdf, deriveIdentity, deriveIdentityFromSeed, deriveMasterSeedFromPassword, ejectAgentPrompt, ejectAgentTools, emptyMap, emptyRegister, encodeToHex, encryptAesGcm, exportPublicKeyBase64, exportPublicKeyHex, generateAuthProof, generatePkce, getMuhkooAppDescriptor, getSubtle, initBn128Wasm, initRsWasm, initializeCircuits, isNewer, isOfflineCapable, isProvisional, manifestToStat, materializeRow, mergeMap, mergeMessage, mergeRegister, messageKey, pack, padHandle, poseidonHash, provisionalHandle, quickVerify, randomBytes, randomState, sealSeed, shardHash, signMessage, stableStringify, toField, tombstoneRow, unpack, unsealSeed, unwrapWithPassphrase, verify, verifyGroth16, verifyHashKnowledge, verifyPreimagePoK, wrapWithPassphrase, writeBeatsTombstone, writeColumns, writeRegister };
5222
- export type { ACL, ACLEntry, AgentConfig, AgentCreateInput, AgentDbToolMode, AgentProvisioned, AgentScopeOpts, AgentSkill, AgentToolsConfig, AgentTrigger, AgentTriggerType, AgentUpdateInput, AgentsNamespaceDeps, AuthClientOptions, AuthKeyPair, AuthSuccess, AuthToken, AuthType, AuthUser, Bn128WasmInstance, BroadcastChannelEvent, BroadcastChannelOptions, CachedSpaceMessage, ChannelLike, ChunkKeyMaterial, ChunkManifest, CipherFrame, CipherMessage, CipherMessageHeader, CircuitBufferConfig, CircuitUrls, ClientOptions, ConnectivityOptions, ConnectivityState, DbQueryPage, DehydratedKeypair, DehydratedKeys, DoubleRatchetCipherDeps, ECDHCurve, EncodeResult, EphemeralEvent, EpochKeyProvider, FileManifest, FileNamespaceDeps, FileStat, FileStorageOptions, FunctionCaps, FunctionConfig, FunctionDeployInput, FunctionInvokeOptions, FunctionScopeOpts, FunctionTrigger, FunctionTriggerType, FunctionTriggers, FunctionUpdateInput, FunctionsNamespaceDeps, Groth16Proof, HlcParts, HlcState, HttpClientOptions, IncomingFrame, KeyExchangeFrame, KeyPair, Keypair, KeyringClientDeps, KeyringTransport, KvEntry, KvNamespaceDeps, LoginOptions, LwwMap, LwwRegister, MessageBody, MessageDeletedEvent, MessageEntry, MessageEvent, MessageHeaders, MessageNamespaceDeps, MessageOp, MessageOptions, MessageSubscription, MuhkooAgentMeta, MuhkooAgentToolsConfig, MuhkooAppDescriptor, MuhkooDBAccess, MuhkooDBMeta, MuhkooFunctionMeta, MuhkooMessageEvent, MuhkooSpaceMeta, OAuthProvider, OfflineEntry, OfflineManagerDeps, OfflineStore, PacketCipher, PacketHeaders, PacketOptions, Participant, Permission, PermissionCheckRequest, PermissionCheckResult, PersonalSpaceClientOptions, Pkce, PublicKeyJWK, QueueEntry, RatchetState, ReceiveResult, RegisterParams, Replayer, Role, SpaceDeps as RoomDeps, SpaceFileMetadata as RoomFileMetadata, SealedHandoff, SealedHeaders, Session, SessionOptions, SessionStore, SetOptions, ShardByteCache, ShardClientOptions, SharedSpaceClientOptions, SnapshotNamespaceDeps, SnarkProof, SpaceChannelLike, SpaceCursor, SpaceDeps, SpaceFileMetadata, SpaceKeyCache, SpaceKeyringDeps, SpaceMessageEvent, SpaceNamespaceDeps, SpaceOfflineAdapter, StorageChangeEvent, StoreName, StoredSession, Subscription, UserIdentity, VaultFactor, VerificationKey, WSMessage, WSMessageType, WSTransportOptions, WrappedPayload, WriteFileInput, WriteFileOptions, WriteToShardsInput, ZkAuthDeps, ZkAuthenticateRequest, ZkChallengeResponse, ZkCompiled, ZkIdentity, ZkRegisterRequest, ZkRegisterResponse };
5764
+ export { AES_GCM_IV_BYTES, AES_GCM_KEY_BYTES, AES_GCM_TAG_BYTES, AgentsNamespace, AuthClient, AuthNamespace, AuthPublicInput, Authenticator, BlockEngine, BroadcastChannel, BroadcastChannelEvents, ChannelExistsError, ChannelNotFoundError, ChunkCipher, Client, ConnectivityManager, DEFAULT_AUTH_BASE_URL, DEFAULT_BASE_URL, DEFAULT_FN_HOST_SUFFIX, DbCache, DoubleRatchet, DoubleRatchetCipher, DoubleRatchetManager, EncryptedSession, EventCore, EventCoreEvents, Field, Field as FieldElement, FileStorage, FunctionsNamespace, HLC, HashKnowledge, HttpClient, HttpError, IndexedDbStore, KEEPER_MEMBER_ID, KeyStore, KeyringClient, KvCache, KvNamespace, LocalEngineHost, LocalStorageSessionStore, MemorySessionStore, Message, Message as MessageDefault, MessageNamespace, MuhkooAgent, MuhkooDB, MuhkooFunction, MuhkooSpace, NoopStore, OfflineLockedError, OfflineManager, OutboundQueue, P2P_SUBJECT, PBKDF2_DEFAULT_ITERATIONS, PREIMAGE_POK_VERIFICATION_KEY, Packet, PeerExchange, PeerNetwork, PersonalSpaceClient, Poseidon, PreimagePoK, ReedSolomonCodec, Space as Room, SerializeMessage, SessionState, ShardCache, ShardClient, SharedSpaceClient, SnapshotNamespace, Space, SpaceCache, SpaceKeyring, SpaceNamespace, SpacePacketCipher, SpaceSignaler, StorageNamespace, SyncEngine, VaultUnavailableError, WSTransport, WebRtcTransport, WorkerEngineHost, ZERO_HLC, ZkAuth, buildCommitment, compareHlc, compilePrograms, decodeFromHex, decorators, decryptAesGcm, defaultCircuitUrls, defaultSessionStore, deleteBeatsWrite, deleteRegister, deriveAesKeyFromPassphrase, deriveBitsHkdf, deriveIdentity, deriveIdentityFromSeed, deriveMasterSeedFromPassword, ejectAgentPrompt, ejectAgentTools, emptyMap, emptyRegister, encodeToHex, encryptAesGcm, exportPublicKeyBase64, exportPublicKeyHex, generateAuthProof, generatePkce, getMuhkooAppDescriptor, getSubtle, initBn128Wasm, initRsWasm, initializeCircuits, isNewer, isOfflineCapable, isP2pCapable, isProvisional, manifestToStat, materializeRow, mergeMap, mergeMessage, mergeRegister, messageKey, pack, padHandle, poseidonHash, protocol, provisionalHandle, quickVerify, randomBytes, randomState, sealSeed, shardHash, signMessage, stableStringify, toField, tombstoneRow, unpack, unsealSeed, unwrapWithPassphrase, verify, verifyGroth16, verifyHashKnowledge, verifyPreimagePoK, wrapWithPassphrase, writeBeatsTombstone, writeColumns, writeRegister };
5765
+ export type { ACL, ACLEntry, AgentConfig, AgentCreateInput, AgentDbToolMode, AgentProvisioned, AgentScopeOpts, AgentSkill, AgentToolsConfig, AgentTrigger, AgentTriggerType, AgentUpdateInput, AgentsNamespaceDeps, AttachedPeerNetwork, AuthClientOptions, AuthKeyPair, AuthSuccess, AuthToken, AuthType, AuthUser, BlockStore, Bn128WasmInstance, BroadcastChannelEvent, BroadcastChannelOptions, CachedSpaceMessage, ChannelLike, ChunkKeyMaterial, ChunkManifest, CipherFrame, CipherMessage, CipherMessageHeader, CircuitBufferConfig, CircuitUrls, ClientOptions, ConnectivityOptions, ConnectivityState, DbQueryPage, DehydratedKeypair, DehydratedKeys, DoubleRatchetCipherDeps, ECDHCurve, EncodeResult, EngineHandle, EngineHost, EphemeralEvent, EpochKeyProvider, FileManifest, FileNamespaceDeps, FileStat, FileStorageOptions, FunctionCaps, FunctionConfig, FunctionDeployInput, FunctionInvokeOptions, FunctionScopeOpts, FunctionTrigger, FunctionTriggerType, FunctionTriggers, FunctionUpdateInput, FunctionsNamespaceDeps, Groth16Proof, Hasher, HlcParts, HlcState, HttpClientOptions, IncomingFrame, KeyExchangeFrame, KeyPair, Keypair, KeyringClientDeps, KeyringTransport, KvEntry, KvNamespaceDeps, LoginOptions, LwwMap, LwwRegister, MessageBody, MessageDeletedEvent, MessageEntry, MessageEvent, MessageHeaders, MessageNamespaceDeps, MessageOp, MessageOptions, MessageSubscription, MuhkooAgentMeta, MuhkooAgentToolsConfig, MuhkooAppDescriptor, MuhkooDBAccess, MuhkooDBMeta, MuhkooFunctionMeta, MuhkooMessageEvent, MuhkooSpaceMeta, OAuthProvider, OfflineEntry, OfflineManagerDeps, OfflineStore, OutSink, PacketCipher, PacketHeaders, PacketOptions, Participant, PeerBlockSource, PeerExchangeOptions, PeerId, PeerNetworkOptions, PeerTransport, Permission, PermissionCheckRequest, PermissionCheckResult, PersonalSpaceClientOptions, Pkce, PublicKeyJWK, QueueEntry, RatchetState, ReceiveResult, RegisterParams, Replayer, Role, SpaceDeps as RoomDeps, SpaceFileMetadata as RoomFileMetadata, SealedHandoff, SealedHeaders, Session, SessionOptions, SessionStore, SetOptions, ShardByteCache, ShardClientOptions, SharedSpaceClientOptions, SignalEnvelope, SignalKind, SignalingSpace, SnapshotNamespaceDeps, SnarkProof, SpaceChannelLike, SpaceCursor, SpaceDeps, SpaceFileMetadata, SpaceKeyCache, SpaceKeyringDeps, SpaceMessageEvent, SpaceNamespaceDeps, SpaceOfflineAdapter, StorageChangeEvent, StoreName, StoredSession, Subscription, UserIdentity, VaultFactor, VerificationKey, WSMessage, WSMessageType, WSTransportOptions, WebRtcTransportOptions, WrappedPayload, WriteFileInput, WriteFileOptions, WriteToShardsInput, ZkAuthDeps, ZkAuthenticateRequest, ZkChallengeResponse, ZkCompiled, ZkIdentity, ZkRegisterRequest, ZkRegisterResponse };