@fairfox/polly 0.20.0 → 0.21.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.
Files changed (81) hide show
  1. package/README.md +11 -0
  2. package/dist/src/background/index.js +22 -12
  3. package/dist/src/background/index.js.map +8 -8
  4. package/dist/src/background/message-router.js +22 -12
  5. package/dist/src/background/message-router.js.map +8 -8
  6. package/dist/src/client/index.js +187 -154
  7. package/dist/src/client/index.js.map +4 -4
  8. package/dist/src/elysia/index.d.ts +2 -0
  9. package/dist/src/elysia/index.js +195 -25
  10. package/dist/src/elysia/index.js.map +8 -5
  11. package/dist/src/elysia/peer-repo-plugin.d.ts +79 -0
  12. package/dist/src/elysia/plugin.d.ts +3 -3
  13. package/dist/src/elysia/signaling-server-plugin.d.ts +121 -0
  14. package/dist/src/index.d.ts +36 -0
  15. package/dist/src/index.js +1752 -13
  16. package/dist/src/index.js.map +31 -13
  17. package/dist/src/shared/adapters/index.js +22 -12
  18. package/dist/src/shared/adapters/index.js.map +7 -7
  19. package/dist/src/shared/lib/_client-only.d.ts +38 -0
  20. package/dist/src/shared/lib/access.d.ts +124 -0
  21. package/dist/src/shared/lib/blob-ref.d.ts +72 -0
  22. package/dist/src/shared/lib/context-helpers.js +22 -12
  23. package/dist/src/shared/lib/context-helpers.js.map +8 -8
  24. package/dist/src/shared/lib/crdt-specialised.d.ts +129 -0
  25. package/dist/src/shared/lib/crdt-state.d.ts +86 -0
  26. package/dist/src/shared/lib/encryption.d.ts +117 -0
  27. package/dist/src/shared/lib/errors.js +19 -9
  28. package/dist/src/shared/lib/errors.js.map +2 -2
  29. package/dist/src/shared/lib/mesh-network-adapter.d.ts +130 -0
  30. package/dist/src/shared/lib/mesh-signaling-client.d.ts +85 -0
  31. package/dist/src/shared/lib/mesh-state.d.ts +102 -0
  32. package/dist/src/shared/lib/mesh-webrtc-adapter.d.ts +132 -0
  33. package/dist/src/shared/lib/message-bus.js +22 -12
  34. package/dist/src/shared/lib/message-bus.js.map +8 -8
  35. package/dist/src/shared/lib/migrate-primitive.d.ts +100 -0
  36. package/dist/src/shared/lib/pairing.d.ts +170 -0
  37. package/dist/src/shared/lib/peer-relay-adapter.d.ts +80 -0
  38. package/dist/src/shared/lib/peer-repo-server.d.ts +83 -0
  39. package/dist/src/shared/lib/peer-state.d.ts +117 -0
  40. package/dist/src/shared/lib/primitive-registry.d.ts +88 -0
  41. package/dist/src/shared/lib/resource.js +22 -12
  42. package/dist/src/shared/lib/resource.js.map +5 -5
  43. package/dist/src/shared/lib/revocation.d.ts +126 -0
  44. package/dist/src/shared/lib/schema-version.d.ts +129 -0
  45. package/dist/src/shared/lib/signing.d.ts +118 -0
  46. package/dist/src/shared/lib/state.js +22 -12
  47. package/dist/src/shared/lib/state.js.map +5 -5
  48. package/dist/src/shared/lib/test-helpers.js +19 -9
  49. package/dist/src/shared/lib/test-helpers.js.map +2 -2
  50. package/dist/src/shared/state/app-state.js +22 -12
  51. package/dist/src/shared/state/app-state.js.map +6 -6
  52. package/dist/src/shared/types/messages.js +19 -9
  53. package/dist/src/shared/types/messages.js.map +2 -2
  54. package/dist/tools/init/src/cli.js +6 -2
  55. package/dist/tools/init/src/cli.js.map +3 -3
  56. package/dist/tools/quality/src/index.js +177 -0
  57. package/dist/tools/quality/src/index.js.map +10 -0
  58. package/dist/tools/test/src/adapters/index.d.ts +2 -2
  59. package/dist/tools/test/src/adapters/index.js +19 -9
  60. package/dist/tools/test/src/adapters/index.js.map +5 -5
  61. package/dist/tools/test/src/browser/harness.d.ts +80 -0
  62. package/dist/tools/test/src/browser/index.d.ts +32 -0
  63. package/dist/tools/test/src/browser/index.js +243 -0
  64. package/dist/tools/test/src/browser/index.js.map +10 -0
  65. package/dist/tools/test/src/browser/run.d.ts +26 -0
  66. package/dist/tools/test/src/index.js +19 -9
  67. package/dist/tools/test/src/index.js.map +5 -5
  68. package/dist/tools/test/src/test-utils.js +19 -9
  69. package/dist/tools/test/src/test-utils.js.map +2 -2
  70. package/dist/tools/verify/specs/tla/MeshState.cfg +21 -0
  71. package/dist/tools/verify/specs/tla/MeshState.tla +247 -0
  72. package/dist/tools/verify/specs/tla/PeerState.cfg +27 -0
  73. package/dist/tools/verify/specs/tla/PeerState.tla +238 -0
  74. package/dist/tools/verify/specs/tla/README.md +27 -3
  75. package/dist/tools/verify/src/cli.js +10 -6
  76. package/dist/tools/verify/src/cli.js.map +10 -10
  77. package/dist/tools/verify/src/config.js +19 -9
  78. package/dist/tools/verify/src/config.js.map +2 -2
  79. package/dist/tools/visualize/src/cli.js +6 -2
  80. package/dist/tools/visualize/src/cli.js.map +8 -8
  81. package/package.json +52 -12
@@ -0,0 +1,102 @@
1
+ /**
2
+ * mesh-state — Phase 2 wrappers exposing $meshState, $meshText, $meshCounter,
3
+ * and $meshList. These are the application-facing constructors for the
4
+ * strongest resilience tier in RFC-041: every device is a full Automerge
5
+ * replica, the server is *not on the data path at all*, and the application
6
+ * functions with zero server uptime once direct peer connections are
7
+ * established.
8
+ *
9
+ * Each primitive wraps the corresponding Phase 0 base ($crdtState, $crdtText,
10
+ * $crdtCounter, $crdtList) with three additions:
11
+ *
12
+ * 1. The `primitive` label is hard-coded to "meshState" so the
13
+ * primitive-registry collision detection knows which family the key
14
+ * belongs to.
15
+ *
16
+ * 2. A handle factory that resolves the application's logical key to an
17
+ * Automerge DocumentId via a per-Repo key map, identical in shape to
18
+ * the $peerState factory but registered against a separate Repo
19
+ * configured for the mesh transport (signed and encrypted at the
20
+ * network layer).
21
+ *
22
+ * 3. Signing and encryption are mandatory, not optional. Where $peerState
23
+ * accepts encrypt/sign as opt-in flags (currently throwing in Phase 1),
24
+ * $meshState requires every operation to be signed by the originating
25
+ * peer's key and encrypted under the document's symmetric key. The
26
+ * mechanism lives in the wrapping MeshNetworkAdapter that the Repo
27
+ * uses for transport.
28
+ *
29
+ * The Repo itself is supplied by the application via {@link configureMeshState}
30
+ * or per-call via the `repo` option. In Phase 2 the production transport will
31
+ * be a WebRTC mesh adapter wrapping signing+encryption around an in-process
32
+ * RTCDataChannel; for tests and for the early Phase 2 cut, an in-memory
33
+ * loopback adapter pair satisfies the same contract.
34
+ */
35
+ import type { Repo } from "@automerge/automerge-repo";
36
+ import type { Access } from "./access";
37
+ import { type SpecialisedPrimitive } from "./crdt-specialised";
38
+ import { type CrdtPrimitive } from "./crdt-state";
39
+ import type { Migrations, VersionedDoc } from "./schema-version";
40
+ /** Common option shape across all four $mesh* primitives. */
41
+ export interface MeshStateOptions {
42
+ /** Override the default Repo for this primitive. The Repo must be
43
+ * configured with the mesh transport (signing and encryption at the
44
+ * network layer). */
45
+ repo?: Repo;
46
+ /** Schema version target for the application. Migrations run on load. */
47
+ schemaVersion?: number;
48
+ /** Migration table keyed by target version. Required if schemaVersion is set. */
49
+ migrations?: Migrations;
50
+ /** Declarative read/write access. The mesh transport compiles this into
51
+ * a public-key set used by the signing layer to verify incoming ops. */
52
+ access?: Access;
53
+ }
54
+ /**
55
+ * Set the default Repo that the $mesh* primitives use when no `repo` option
56
+ * is supplied. Calling this with a new Repo clears the per-Repo key map so
57
+ * that tests start each scenario with a fresh document space.
58
+ *
59
+ * Production code typically calls this once at application startup with a
60
+ * Repo configured for the mesh transport. Tests call it before each
61
+ * scenario with an in-memory or loopback Repo.
62
+ */
63
+ export declare function configureMeshState(repo: Repo): void;
64
+ /**
65
+ * Reset the mesh-state subsystem to its initial unconfigured state.
66
+ * Intended for tests; production code should not call this.
67
+ */
68
+ export declare function resetMeshState(): void;
69
+ /**
70
+ * Create a peer-replicated state primitive backed by Automerge with a mesh
71
+ * transport. Every device holds a full replica; no central server holds a
72
+ * replica. Operations flow peer-to-peer through signed and encrypted
73
+ * messages on the underlying transport.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * const journal = $meshState<Journal>("journal", { entries: [] });
78
+ * await journal.loaded;
79
+ * journal.value = { entries: ["my private thoughts"] };
80
+ * ```
81
+ */
82
+ export declare function $meshState<T extends VersionedDoc>(key: string, initialValue: T, options?: MeshStateOptions): CrdtPrimitive<T>;
83
+ /**
84
+ * Create a peer-replicated text primitive backed by a mesh transport.
85
+ * Concurrent character-level edits from peers merge cleanly via Automerge's
86
+ * updateText splicing, and every operation is signed and encrypted before
87
+ * leaving the originating peer.
88
+ */
89
+ export declare function $meshText(key: string, initialValue: string, options?: MeshStateOptions): SpecialisedPrimitive<string>;
90
+ /**
91
+ * Create a peer-replicated counter primitive backed by a mesh transport.
92
+ * Concurrent increments commute, and every operation is signed and
93
+ * encrypted before leaving the originating peer.
94
+ */
95
+ export declare function $meshCounter(key: string, initialValue: number, options?: MeshStateOptions): SpecialisedPrimitive<number>;
96
+ /**
97
+ * Create a peer-replicated list primitive backed by a mesh transport.
98
+ * Phase 0 naive replacement applies to writes for now; Phase 1.1 will
99
+ * refine with structural diff-to-splice for concurrent insert/remove
100
+ * preservation.
101
+ */
102
+ export declare function $meshList<T>(key: string, initialValue: T[], options?: MeshStateOptions): SpecialisedPrimitive<T[]>;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * mesh-webrtc-adapter — Phase 2 browser-side WebRTC transport for Polly's
3
+ * $meshState primitive. Extends Automerge's NetworkAdapter base class and
4
+ * uses real native RTCPeerConnection / RTCDataChannel instances to carry
5
+ * sync messages between peers.
6
+ *
7
+ * This is the "base" transport that MeshNetworkAdapter wraps with its
8
+ * sign-then-encrypt envelope. The stack is:
9
+ *
10
+ * $meshState
11
+ * └─ Repo
12
+ * └─ MeshNetworkAdapter (sign + encrypt)
13
+ * └─ MeshWebRTCAdapter (real data channels)
14
+ * └─ MeshSignalingClient (SDP/ICE relay)
15
+ * └─ signalingServer (Elysia plugin on the host app)
16
+ *
17
+ * Because WebRTC lives in browsers, this module is browser-only. It
18
+ * assumes global RTCPeerConnection, RTCDataChannel, and WebSocket types
19
+ * are available. Under bun:test the classes cannot be exercised
20
+ * end-to-end — the first validation path is either Playwright running a
21
+ * real browser, a Puppeteer harness, or a human testing a browser-side
22
+ * example app that consumes the adapter.
23
+ *
24
+ * What this module does at runtime:
25
+ *
26
+ * - Constructs a MeshWebRTCAdapter with a signalling client and a local
27
+ * peer id. No data channels exist at construction time.
28
+ *
29
+ * - When Automerge's NetworkSubsystem calls connect(peerId) on the
30
+ * adapter, it starts listening for signals from remote peers and is
31
+ * ready to build peer connections as they are discovered.
32
+ *
33
+ * - When a remote peer sends an SDP offer via the signalling channel,
34
+ * the adapter builds a fresh RTCPeerConnection, accepts the offer,
35
+ * produces an answer, sends it back through signalling, and wires the
36
+ * received data channel to emit Automerge Message events upward.
37
+ *
38
+ * - When the local repo calls send(message), the adapter looks up the
39
+ * peer connection for message.targetId and writes the serialised
40
+ * bytes to its data channel. If no connection exists yet, the adapter
41
+ * creates one by sending an SDP offer through signalling. Outgoing
42
+ * messages are queued until the channel is open.
43
+ *
44
+ * - Disconnect tears down every peer connection and closes the
45
+ * signalling client.
46
+ */
47
+ import { type Message, NetworkAdapter, type PeerId, type PeerMetadata } from "@automerge/automerge-repo";
48
+ import type { MeshSignalingClient } from "./mesh-signaling-client";
49
+ /** Standard STUN servers for NAT traversal. In production, callers who
50
+ * need TURN fallback for peers behind symmetric NATs should replace this
51
+ * with their own ICE server list. */
52
+ export declare const DEFAULT_ICE_SERVERS: RTCIceServer[];
53
+ /** Options for constructing a {@link MeshWebRTCAdapter}. */
54
+ export interface MeshWebRTCAdapterOptions {
55
+ /** The signalling client the adapter uses to exchange SDP and ICE
56
+ * candidates with other peers. Typically constructed once per
57
+ * application and shared across any adapters that need it. */
58
+ signaling: MeshSignalingClient;
59
+ /** The local peer id. Must match the peer id the signalling client
60
+ * registered with. */
61
+ peerId: string;
62
+ /** Peer ids to connect to on startup. When `connect()` is called, the
63
+ * adapter iterates this list and initiates a WebRTC connection to each
64
+ * one by sending an SDP offer through the signalling channel. Peers
65
+ * not in this list can still connect by sending an offer *to* this
66
+ * adapter. The natural source for this list is the keyring's
67
+ * knownPeers map keys. */
68
+ knownPeerIds?: string[];
69
+ /** Optional ICE server list override. Defaults to {@link DEFAULT_ICE_SERVERS}. */
70
+ iceServers?: RTCIceServer[];
71
+ /** Optional data channel label. Defaults to "polly-mesh". Applications
72
+ * that share a signalling server between multiple meshes may want
73
+ * distinct labels per mesh. */
74
+ dataChannelLabel?: string;
75
+ }
76
+ /**
77
+ * Automerge-Repo NetworkAdapter backed by real WebRTC data channels.
78
+ * Manages one RTCPeerConnection per remote peer and uses a supplied
79
+ * {@link MeshSignalingClient} for SDP/ICE exchange.
80
+ */
81
+ export declare class MeshWebRTCAdapter extends NetworkAdapter {
82
+ readonly signaling: MeshSignalingClient;
83
+ readonly iceServers: RTCIceServer[];
84
+ readonly dataChannelLabel: string;
85
+ readonly knownPeerIds: string[];
86
+ private readonly slots;
87
+ private ready;
88
+ private readyResolver;
89
+ constructor(options: MeshWebRTCAdapterOptions);
90
+ isReady(): boolean;
91
+ whenReady(): Promise<void>;
92
+ /**
93
+ * Start the adapter. Wires the signalling client's onSignal callback
94
+ * to the adapter's dispatch, opens the signalling connection if it
95
+ * is not already open, and marks the adapter ready.
96
+ */
97
+ connect(peerId: PeerId, peerMetadata?: PeerMetadata): void;
98
+ disconnect(): void;
99
+ /**
100
+ * Send a sync message to a specific remote peer. If no RTCPeerConnection
101
+ * exists yet, the adapter initiates one by producing an SDP offer and
102
+ * sending it through the signalling channel; the outgoing bytes are
103
+ * queued until the data channel is open.
104
+ */
105
+ send(message: Message): void;
106
+ /**
107
+ * Entry point the signalling client calls when it receives a signal
108
+ * from a remote peer. Dispatches on the payload `kind` to either
109
+ * accept an offer (building an answer), apply an answer, or add an
110
+ * ICE candidate. Exposed publicly so the caller that constructs the
111
+ * signalling client can wire the onSignal callback directly to this
112
+ * method.
113
+ */
114
+ handleSignal(fromPeerId: string, rawPayload: unknown): void;
115
+ private createInitiatingSlot;
116
+ private initiateOffer;
117
+ private handleOffer;
118
+ private handleAnswer;
119
+ private handleIceCandidate;
120
+ private wireConnection;
121
+ private wireDataChannel;
122
+ private dispatchMessage;
123
+ /** Pack an Automerge Message into binary for transmission over the
124
+ * data channel. The format mirrors MeshNetworkAdapter's internal
125
+ * serialisation: a length-prefixed JSON header followed by the raw
126
+ * data bytes. Returns a Uint8Array<ArrayBuffer> so the result is
127
+ * directly usable by RTCDataChannel.send under TypeScript's strict
128
+ * buffer-source typing. */
129
+ private serialiseMessage;
130
+ /** Inverse of {@link serialiseMessage}. */
131
+ private deserialiseMessage;
132
+ }
@@ -2,27 +2,37 @@ var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __moduleCache = /* @__PURE__ */ new WeakMap;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
6
8
  var __toCommonJS = (from) => {
7
- var entry = __moduleCache.get(from), desc;
9
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
8
10
  if (entry)
9
11
  return entry;
10
12
  entry = __defProp({}, "__esModule", { value: true });
11
- if (from && typeof from === "object" || typeof from === "function")
12
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
- get: () => from[key],
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- }));
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (var key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(entry, key))
16
+ __defProp(entry, key, {
17
+ get: __accessProp.bind(from, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
16
21
  __moduleCache.set(from, entry);
17
22
  return entry;
18
23
  };
24
+ var __moduleCache;
25
+ var __returnValue = (v) => v;
26
+ function __exportSetter(name, newValue) {
27
+ this[name] = __returnValue.bind(null, newValue);
28
+ }
19
29
  var __export = (target, all) => {
20
30
  for (var name in all)
21
31
  __defProp(target, name, {
22
32
  get: all[name],
23
33
  enumerable: true,
24
34
  configurable: true,
25
- set: (newValue) => all[name] = () => newValue
35
+ set: __exportSetter.bind(all, name)
26
36
  });
27
37
  };
28
38
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -259,7 +269,9 @@ class BroadcastChannelSyncAdapter {
259
269
  channel = null;
260
270
  listeners = [];
261
271
  constructor(channelName = "polly-sync") {
262
- if (typeof BroadcastChannel !== "undefined") {
272
+ if (typeof BroadcastChannel === "undefined") {
273
+ console.warn("[SyncAdapter] BroadcastChannel not available");
274
+ } else {
263
275
  this.channel = new BroadcastChannel(channelName);
264
276
  this.channel.onmessage = (event) => {
265
277
  if (event.data.type === "STATE_SYNC") {
@@ -268,8 +280,6 @@ class BroadcastChannelSyncAdapter {
268
280
  });
269
281
  }
270
282
  };
271
- } else {
272
- console.warn("[SyncAdapter] BroadcastChannel not available");
273
283
  }
274
284
  }
275
285
  broadcast(message) {
@@ -1529,4 +1539,4 @@ export {
1529
1539
  MessageBus
1530
1540
  };
1531
1541
 
1532
- //# debugId=183F35797010AB4564756E2164756E21
1542
+ //# debugId=7BE490ECD586102364756E2164756E21