@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,79 @@
1
+ /**
2
+ * peerRepo — Elysia plugin that runs a Polly peer-relay server alongside an
3
+ * Elysia application and exposes the server's Repo on the Elysia context.
4
+ *
5
+ * The Phase 1 plan originally framed the relay server as an Elysia plugin
6
+ * that registers a WebSocket route on Elysia's native socket layer. That
7
+ * design does not work directly: Automerge's WebSocketServerAdapter requires
8
+ * an `isomorphic-ws` WebSocketServer instance, and Elysia's WebSocket layer
9
+ * is built on Bun's native WebSocket API, which is a different shape. The
10
+ * realistic Phase 1 cut is therefore a *lifecycle* plugin: it runs
11
+ * createPeerRepoServer on its own port during Elysia startup, decorates the
12
+ * Elysia context so route handlers can reach the resulting Repo, and tears
13
+ * the server down during Elysia shutdown. Authenticated WebSocket upgrades
14
+ * via Elysia's hook system are a Phase 1.1 follow-up that requires either
15
+ * a custom Bun-native NetworkAdapter or an upgrade-time auth bridge.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { Elysia } from "elysia";
20
+ * import { peerRepo } from "@fairfox/polly/elysia";
21
+ *
22
+ * const app = new Elysia()
23
+ * .use(peerRepo({ port: 3030, storagePath: "./data/polly-peer" }))
24
+ * .get("/stats", ({ pollyPeerRepo }) => {
25
+ * return { peers: pollyPeerRepo.peers.length };
26
+ * })
27
+ * .listen(8080);
28
+ * ```
29
+ */
30
+ import { Elysia } from "elysia";
31
+ import { type CreatePeerRepoServerOptions, type PeerRepoServer } from "../shared/lib/peer-repo-server";
32
+ export interface PeerRepoPluginOptions extends CreatePeerRepoServerOptions {
33
+ /** Optional path for a health endpoint that returns peer count and storage
34
+ * id. Set to false to skip the health route entirely. Defaults to
35
+ * "/polly/peer/health". */
36
+ healthPath?: string | false;
37
+ }
38
+ /**
39
+ * Elysia plugin that boots a Polly peer-relay server alongside the host app
40
+ * and decorates the context with `pollyPeerRepo` (the Repo) and
41
+ * `pollyPeerServer` (the full {@link PeerRepoServer} for advanced use).
42
+ *
43
+ * The plugin starts the relay server during Elysia's onStart lifecycle and
44
+ * shuts it down during onStop, so route handlers and cron jobs can use the
45
+ * decorated Repo throughout the request lifetime without managing the
46
+ * underlying transport themselves.
47
+ */
48
+ export declare function peerRepo(options: PeerRepoPluginOptions): Elysia<"", {
49
+ decorator: {
50
+ pollyPeerRepo: import("@automerge/automerge-repo").Repo | null;
51
+ } & {
52
+ pollyPeerServer: PeerRepoServer | null;
53
+ };
54
+ store: {};
55
+ derive: {};
56
+ resolve: {};
57
+ }, {
58
+ typebox: {};
59
+ error: {};
60
+ }, {
61
+ schema: {};
62
+ standaloneSchema: {};
63
+ macro: {};
64
+ macroFn: {};
65
+ parser: {};
66
+ response: {};
67
+ }, {}, {
68
+ derive: {};
69
+ resolve: {};
70
+ schema: {};
71
+ standaloneSchema: {};
72
+ response: {};
73
+ }, {
74
+ derive: {};
75
+ resolve: {};
76
+ schema: {};
77
+ standaloneSchema: {};
78
+ response: {};
79
+ }>;
@@ -60,10 +60,10 @@ export declare function polly(config?: PollyConfig): Elysia<"", {
60
60
  }, {
61
61
  [x: string]: {
62
62
  subscribe: {
63
- body: unknown;
63
+ body: {};
64
64
  params: {};
65
- query: unknown;
66
- headers: unknown;
65
+ query: {};
66
+ headers: {};
67
67
  response: {};
68
68
  };
69
69
  };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * signalingServer — Phase 2 Elysia plugin that exposes a stateless
3
+ * WebSocket route for SDP/ICE relay between $meshState peers.
4
+ *
5
+ * The mesh transport is a star-of-data-channels: peers establish direct
6
+ * WebRTC connections to each other and exchange document operations
7
+ * peer-to-peer once those channels are open. WebRTC connection setup
8
+ * needs an out-of-band channel for SDP offer/answer and ICE candidate
9
+ * exchange, and that channel is what this plugin provides. The plugin
10
+ * does not own any document state, does not hold any encryption keys,
11
+ * and never inspects the contents of the messages it relays. It is a
12
+ * pure pub-sub by peer id.
13
+ *
14
+ * Once two peers have completed the SDP exchange and opened a direct
15
+ * data channel, the signalling server is no longer on the critical
16
+ * path — the peers talk directly. The signalling server's role is
17
+ * therefore intermittent: peers connect to it only during the brief
18
+ * windows when they are establishing or re-establishing connections.
19
+ *
20
+ * Wire protocol:
21
+ *
22
+ * Client → server (join):
23
+ * { type: "join", peerId: "peer-alice" }
24
+ *
25
+ * Client → server (signal to another peer):
26
+ * { type: "signal", peerId: "peer-alice", targetPeerId: "peer-bob",
27
+ * payload: { ... } }
28
+ *
29
+ * Server → client (delivered signal):
30
+ * { type: "signal", peerId: "peer-alice", targetPeerId: "peer-bob",
31
+ * payload: { ... } }
32
+ *
33
+ * Server → client (notification of unknown target):
34
+ * { type: "error", reason: "unknown-target", targetPeerId: "..." }
35
+ *
36
+ * The `payload` is opaque to the signalling server — typically it
37
+ * carries an SDP offer, SDP answer, or ICE candidate. Applications can
38
+ * also use the relay for any other peer-to-peer message that needs an
39
+ * intermediary, such as the initial handshake of a pairing flow.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { Elysia } from "elysia";
44
+ * import { signalingServer } from "@fairfox/polly/elysia";
45
+ *
46
+ * const app = new Elysia()
47
+ * .use(signalingServer({ path: "/polly/signaling" }))
48
+ * .listen(8080);
49
+ * ```
50
+ */
51
+ import { Elysia } from "elysia";
52
+ /** A signalling message. The `type` discriminates between join (peer
53
+ * registration), signal (relayed message), and error (server response). */
54
+ export type SignalingMessage = {
55
+ type: "join";
56
+ /** The peer registering itself with the signalling server. */
57
+ peerId: string;
58
+ } | {
59
+ type: "signal";
60
+ /** The peer sending the signal. */
61
+ peerId: string;
62
+ /** The peer the signal is being relayed to. */
63
+ targetPeerId: string;
64
+ /** Opaque payload, typically SDP or ICE. */
65
+ payload: unknown;
66
+ } | {
67
+ type: "error";
68
+ reason: "unknown-target" | "not-joined" | "malformed";
69
+ targetPeerId?: string;
70
+ };
71
+ export interface SignalingServerOptions {
72
+ /** WebSocket route path. Defaults to "/polly/signaling". */
73
+ path?: string;
74
+ }
75
+ /**
76
+ * Construct the signalling-server Elysia plugin. The plugin keeps a
77
+ * per-instance map of peer id → WebSocket connection so that incoming
78
+ * "signal" messages can be routed to the right target socket. The map
79
+ * is local to the plugin instance, not shared across processes; for
80
+ * multi-instance deployments behind a load balancer, applications need
81
+ * sticky connection routing or a shared backplane (Redis pub-sub or
82
+ * similar). That is a follow-up.
83
+ */
84
+ export declare function signalingServer(options?: SignalingServerOptions): Elysia<"", {
85
+ decorator: {};
86
+ store: {};
87
+ derive: {};
88
+ resolve: {};
89
+ }, {
90
+ typebox: {};
91
+ error: {};
92
+ }, {
93
+ schema: {};
94
+ standaloneSchema: {};
95
+ macro: {};
96
+ macroFn: {};
97
+ parser: {};
98
+ response: {};
99
+ }, {
100
+ [x: string]: {
101
+ subscribe: {
102
+ body: {};
103
+ params: {};
104
+ query: {};
105
+ headers: {};
106
+ response: {};
107
+ };
108
+ };
109
+ }, {
110
+ derive: {};
111
+ resolve: {};
112
+ schema: {};
113
+ standaloneSchema: {};
114
+ response: {};
115
+ }, {
116
+ derive: {};
117
+ resolve: {};
118
+ schema: {};
119
+ standaloneSchema: {};
120
+ response: {};
121
+ }>;
@@ -6,14 +6,50 @@
6
6
  */
7
7
  export type { ExtensionAdapters } from "./shared/adapters";
8
8
  export { createChromeAdapters } from "./shared/adapters";
9
+ export type { Access, AccessPredicate, PeerIdentity } from "./shared/lib/access";
10
+ export { and, anyOfPeers, anyone, groupAccess, nobody, not, onlyPeer, or, ownerAccess, publicAccess, readOnlyExcept, } from "./shared/lib/access";
11
+ export type { BlobRef, CreateBlobRefArgs } from "./shared/lib/blob-ref";
12
+ export { computeBlobHash, createBlobRef, isBlobRef } from "./shared/lib/blob-ref";
9
13
  export { checkPostconditions, checkPreconditions, clearConstraints, isRuntimeConstraintsEnabled, registerConstraint, registerConstraints, } from "./shared/lib/constraints";
10
14
  export type { ContextConfig } from "./shared/lib/context-helpers";
11
15
  export { createContext, runInContext } from "./shared/lib/context-helpers";
12
16
  export type { BackgroundHelpers, ContentScriptHelpers, DevToolsHelpers, OptionsHelpers, PopupHelpers, SidePanelHelpers, } from "./shared/lib/context-specific-helpers";
17
+ export type { CounterDoc, CrdtCounterOptions, CrdtListOptions, CrdtTextOptions, ListDoc, SpecialisedPrimitive, TextDoc, } from "./shared/lib/crdt-specialised";
18
+ export { $crdtCounter, $crdtList, $crdtText } from "./shared/lib/crdt-specialised";
19
+ export type { CrdtPrimitive, CrdtStateOptions } from "./shared/lib/crdt-state";
20
+ export { $crdtState } from "./shared/lib/crdt-state";
21
+ export type { EncryptedEnvelope, SealedBytes, } from "./shared/lib/encryption";
22
+ export { decrypt, decryptOrThrow, EncryptionError, encrypt, generateDocumentKey, KEY_BYTES as ENCRYPTION_KEY_BYTES, NONCE_BYTES as ENCRYPTION_NONCE_BYTES, TAG_BYTES as ENCRYPTION_TAG_BYTES, } from "./shared/lib/encryption";
13
23
  export { ConnectionError, ErrorHandler, ExtensionError, HandlerError, TimeoutError, } from "./shared/lib/errors";
24
+ export type { MeshKeyring, MeshNetworkAdapterOptions, } from "./shared/lib/mesh-network-adapter";
25
+ export { DEFAULT_MESH_KEY_ID, MeshNetworkAdapter, } from "./shared/lib/mesh-network-adapter";
26
+ export type { MeshSignalingClientOptions, SignalingMessage as MeshSignalingMessage, } from "./shared/lib/mesh-signaling-client";
27
+ export { MeshSignalingClient } from "./shared/lib/mesh-signaling-client";
28
+ export type { MeshStateOptions } from "./shared/lib/mesh-state";
29
+ export { $meshCounter, $meshList, $meshState, $meshText, configureMeshState, resetMeshState, } from "./shared/lib/mesh-state";
30
+ export type { MeshWebRTCAdapterOptions } from "./shared/lib/mesh-webrtc-adapter";
31
+ export { DEFAULT_ICE_SERVERS, MeshWebRTCAdapter } from "./shared/lib/mesh-webrtc-adapter";
14
32
  export { getMessageBus, MessageBus } from "./shared/lib/message-bus";
33
+ export type { MigratableState } from "./shared/lib/migrate-primitive";
34
+ export { MigrationError, migratePrimitive } from "./shared/lib/migrate-primitive";
35
+ export type { CreatePairingTokenOptions, PairingToken, } from "./shared/lib/pairing";
36
+ export { applyPairingToken, createPairingToken, createPairingTokenWithFreshIdentity, DEFAULT_PAIRING_TTL_MS, decodePairingToken, encodePairingToken, isPairingTokenExpired, PAIRING_NONCE_BYTES, PAIRING_TOKEN_VERSION, PairingError, parsePairingToken, serialisePairingToken, } from "./shared/lib/pairing";
37
+ export type { CreatePeerStateClientOptions, PeerRelayConnectionState, PeerStateClient, } from "./shared/lib/peer-relay-adapter";
38
+ export { createPeerStateClient } from "./shared/lib/peer-relay-adapter";
39
+ export type { CreatePeerRepoServerOptions, PeerRepoServer, } from "./shared/lib/peer-repo-server";
40
+ export { createPeerRepoServer } from "./shared/lib/peer-repo-server";
41
+ export type { PeerCounterOptions, PeerListOptions, PeerStateOptions, PeerTextOptions, } from "./shared/lib/peer-state";
42
+ export { $peerCounter, $peerList, $peerState, $peerText, configurePeerState, resetPeerState, } from "./shared/lib/peer-state";
43
+ export type { PrimitiveKind } from "./shared/lib/primitive-registry";
44
+ export { PrimitiveCollisionError } from "./shared/lib/primitive-registry";
15
45
  export type { Resource, ResourceOptions, ResourceStatus } from "./shared/lib/resource";
16
46
  export { $resource } from "./shared/lib/resource";
47
+ export type { CreateRevocationOptions, RevocationRecord, } from "./shared/lib/revocation";
48
+ export { applyRevocation, createRevocation, decodeRevocation, encodeRevocation, REVOCATION_MAGIC, REVOCATION_RECORD_VERSION, RevocationError, revokePeerLocally, } from "./shared/lib/revocation";
49
+ export type { Migration, Migrations, OpVersionCheck, VersionedDoc, } from "./shared/lib/schema-version";
50
+ export { assertOpVersion, checkOpVersion, getDocVersion, SCHEMA_VERSION_FIELD, SchemaVersionError, } from "./shared/lib/schema-version";
51
+ export type { SignedEnvelope, SigningKeyPair } from "./shared/lib/signing";
52
+ export { generateSigningKeyPair, PUBLIC_KEY_BYTES as SIGNING_PUBLIC_KEY_BYTES, SECRET_KEY_BYTES as SIGNING_SECRET_KEY_BYTES, SIGNATURE_BYTES as SIGNING_SIGNATURE_BYTES, SigningError, sign, signingKeyPairFromSecret, verify, } from "./shared/lib/signing";
17
53
  export { $persistedState, $sharedState, $state, $syncedState } from "./shared/lib/state";
18
54
  export type { TestCase, TestSuite } from "./shared/lib/test-helpers";
19
55
  export { createTestSuite, quickTest, TestRunner } from "./shared/lib/test-helpers";