@fairfox/polly 0.21.0 → 0.23.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/README.md +83 -3
- package/dist/cli/polly.js +21 -1
- package/dist/cli/polly.js.map +3 -3
- package/dist/src/elysia/index.js +4 -2
- package/dist/src/elysia/index.js.map +3 -3
- package/dist/src/index.d.ts +0 -32
- package/dist/src/index.js +2 -1642
- package/dist/src/index.js.map +4 -20
- package/dist/src/mesh.d.ts +29 -0
- package/dist/src/mesh.js +1502 -0
- package/dist/src/mesh.js.map +22 -0
- package/dist/src/peer.d.ts +29 -0
- package/dist/src/peer.js +930 -0
- package/dist/src/peer.js.map +20 -0
- package/dist/tools/quality/src/{index.js → cli.js} +112 -83
- package/dist/tools/quality/src/cli.js.map +11 -0
- package/package.json +8 -4
- package/dist/tools/quality/src/index.js.map +0 -10
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @fairfox/polly
|
|
2
2
|
|
|
3
|
-
Reactive state for multi-context apps
|
|
3
|
+
Reactive state for multi-context apps — from single-process signals to peer-to-peer encrypted mesh — with formal verification.
|
|
4
4
|
|
|
5
5
|
## The pitch
|
|
6
6
|
|
|
@@ -86,6 +86,45 @@ todos.status; // Signal<"idle" | "loading" | "success" | "error">
|
|
|
86
86
|
todos.refetch();
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
## Peer-first state (v0.21.0)
|
|
90
|
+
|
|
91
|
+
The four primitives above keep state consistent inside a single deployment. But some applications need state that survives the server going away, or state that the server never sees at all. Polly now offers three resilience tiers, each a distinct primitive family:
|
|
92
|
+
|
|
93
|
+
| Tier | Primitive | Server's role | Resilience |
|
|
94
|
+
|------|-----------|---------------|------------|
|
|
95
|
+
| Weakest | `$sharedState` | Source of truth | Server backups |
|
|
96
|
+
| Middle | `$peerState` | Full peer on the data path | Any device can rehydrate the server |
|
|
97
|
+
| Strongest | `$meshState` | Not on the data path | App works with zero server uptime |
|
|
98
|
+
|
|
99
|
+
**`$peerState`** — every device holds a full [Automerge](https://automerge.org/) CRDT replica. The server holds one too, so cron and HTTP handlers can read and mutate documents. If the server loses its storage, any reconnecting client repopulates it through the normal sync protocol.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { createPeerStateClient, configurePeerState, $peerState } from "@fairfox/polly";
|
|
103
|
+
|
|
104
|
+
const client = createPeerStateClient({ url: "wss://yourapp.com/polly/peer" });
|
|
105
|
+
configurePeerState(client.repo);
|
|
106
|
+
|
|
107
|
+
const settings = $peerState("settings", { theme: "dark" });
|
|
108
|
+
await settings.loaded;
|
|
109
|
+
settings.value = { theme: "light" }; // syncs to every peer
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**`$meshState`** — peers exchange operations directly over WebRTC data channels, signed with Ed25519 and encrypted with XSalsa20-Poly1305. No server sees the data. A small stateless signalling server helps peers find each other; removing it does not affect running connections.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { configureMeshState, $meshState, MeshNetworkAdapter, MeshWebRTCAdapter } from "@fairfox/polly";
|
|
116
|
+
|
|
117
|
+
const repo = new Repo({ network: [new MeshNetworkAdapter({ base: webrtcAdapter, keyring })] });
|
|
118
|
+
configureMeshState(repo);
|
|
119
|
+
|
|
120
|
+
const notes = $meshState("notes", { entries: [] });
|
|
121
|
+
// Operations flow peer-to-peer, signed and encrypted
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
First-time key exchange between devices uses a pairing token displayed as a QR code. Compromised devices are revoked via signed revocation records that propagate to every peer.
|
|
125
|
+
|
|
126
|
+
The three tiers coexist in one application — public settings in `$sharedState`, collaborative documents in `$peerState`, private notes in `$meshState`. See [docs/STATE.md](docs/STATE.md) for the full decision tree and [docs/RFC-041-choosing.md](docs/RFC-041-choosing.md) for the design rationale.
|
|
127
|
+
|
|
89
128
|
## Verification that plugs in
|
|
90
129
|
|
|
91
130
|
A popup and a background script both write to the same state. A content script reads it mid-update. Tests miss these bugs because they depend on timing.
|
|
@@ -206,16 +245,57 @@ polly verify Run formal verification
|
|
|
206
245
|
polly visualize Generate architecture diagrams (Structurizr DSL)
|
|
207
246
|
```
|
|
208
247
|
|
|
248
|
+
## Quality tooling
|
|
249
|
+
|
|
250
|
+
Polly ships conformance checks and a browser test harness that consuming applications can adopt directly.
|
|
251
|
+
|
|
252
|
+
**No-as-casting check.** Bans TypeScript `as` type assertions codebase-wide (only `as const` and the explicit escape hatch `as unknown as` are allowed). Violations include pattern-specific fix advice. Import it programmatically:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { checkNoAsCasting } from "@fairfox/polly/quality";
|
|
256
|
+
|
|
257
|
+
const result = await checkNoAsCasting({ rootDir: process.cwd() });
|
|
258
|
+
if (result.violations.length > 0) {
|
|
259
|
+
result.print();
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Browser test harness.** A Puppeteer-based harness for testing browser-only code (WebRTC adapters, Preact components, anything needing real DOM). Bundles each test file with Bun.build, serves on an ephemeral port, and collects results:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { describe, test, expect, done, flush, cleanup } from "@fairfox/polly/test/browser";
|
|
268
|
+
|
|
269
|
+
describe("my component", () => {
|
|
270
|
+
test("renders", async () => {
|
|
271
|
+
render(<MyComponent />, app);
|
|
272
|
+
await flush();
|
|
273
|
+
expect(app.querySelector("h1")).toHaveTextContent("Hello");
|
|
274
|
+
cleanup(app);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
done();
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Run with `bun tools/test/src/browser/run.ts tests/browser`.
|
|
282
|
+
|
|
209
283
|
## Use with Claude Code
|
|
210
284
|
|
|
211
|
-
This repo ships with a [Claude Code skill](.claude/skills) that
|
|
285
|
+
This repo ships with a [Claude Code skill](.claude/skills) that covers the full Polly API — state primitives, peer-first and mesh state, verification, quality tooling, and the browser test harness. Install it in your project so Claude can help you integrate Polly with full context:
|
|
212
286
|
|
|
213
287
|
```bash
|
|
214
288
|
# From your project directory
|
|
215
289
|
claude install-skill /path/to/polly/.claude/skills
|
|
216
290
|
```
|
|
217
291
|
|
|
218
|
-
Then ask Claude things like
|
|
292
|
+
Then ask Claude things like:
|
|
293
|
+
- "How would Polly fit into this project?"
|
|
294
|
+
- "Add verification to my handlers"
|
|
295
|
+
- "Set up $peerState with an Elysia server"
|
|
296
|
+
- "Wire up the no-as-casting conformance check in CI"
|
|
297
|
+
|
|
298
|
+
If you maintain your own Claude Code skills, consider adding Polly's state-tier decision tree and verification patterns to your project-specific skill so Claude can recommend the right primitive for each piece of state.
|
|
219
299
|
|
|
220
300
|
## Licence
|
|
221
301
|
|
package/dist/cli/polly.js
CHANGED
|
@@ -89,6 +89,22 @@ async function visualize() {
|
|
|
89
89
|
throw new Error(`Visualization failed with exit code ${exitCode}`);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
async function quality() {
|
|
93
|
+
const bundledCli = `${__dirname2}/../tools/quality/src/cli.js`;
|
|
94
|
+
const monorepoCli = `${__dirname2}/../tools/quality/src/cli.ts`;
|
|
95
|
+
const qualityCli = await Bun.file(bundledCli).exists() ? bundledCli : monorepoCli;
|
|
96
|
+
const proc = Bun.spawn(["bun", qualityCli, ...commandArgs], {
|
|
97
|
+
cwd,
|
|
98
|
+
stdout: "inherit",
|
|
99
|
+
stderr: "inherit",
|
|
100
|
+
stdin: "inherit",
|
|
101
|
+
env: process.env
|
|
102
|
+
});
|
|
103
|
+
const exitCode = await proc.exited;
|
|
104
|
+
if (exitCode !== 0) {
|
|
105
|
+
throw new Error(`Quality checks failed with exit code ${exitCode}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
92
108
|
async function typecheck() {
|
|
93
109
|
const proc = Bun.spawn(["bunx", "tsc", "--noEmit"], {
|
|
94
110
|
cwd,
|
|
@@ -191,6 +207,7 @@ Usage:
|
|
|
191
207
|
polly test [args] Run tests
|
|
192
208
|
polly verify [args] Run formal verification
|
|
193
209
|
polly visualize [args] Generate architecture diagrams
|
|
210
|
+
polly quality [args] Run quality conformance checks
|
|
194
211
|
polly help Show this help
|
|
195
212
|
|
|
196
213
|
Options:
|
|
@@ -232,6 +249,9 @@ async function main() {
|
|
|
232
249
|
case "visualize":
|
|
233
250
|
await visualize();
|
|
234
251
|
break;
|
|
252
|
+
case "quality":
|
|
253
|
+
await quality();
|
|
254
|
+
break;
|
|
235
255
|
case "help":
|
|
236
256
|
case "--help":
|
|
237
257
|
case "-h":
|
|
@@ -252,4 +272,4 @@ async function main() {
|
|
|
252
272
|
}
|
|
253
273
|
main();
|
|
254
274
|
|
|
255
|
-
//# debugId=
|
|
275
|
+
//# debugId=B9B6663515E3E39364756E2164756E21
|
package/dist/cli/polly.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../cli/polly.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"#!/usr/bin/env bun\n/**\n * Polly CLI\n *\n * Command-line tool for building multi-execution-context applications\n * with reactive state and cross-context messaging.\n *\n * Supports: Chrome extensions, PWAs, Node/Bun/Deno apps with workers\n *\n * Usage:\n * polly init [name] [--type=TYPE] Create a new project\n * polly check Run all checks (typecheck, lint, test, build)\n * polly build [options] Build the project\n * polly dev Build with watch mode\n * polly typecheck Type check your code\n * polly lint [--fix] Lint your code\n * polly format Format your code\n * polly test [args] Run tests (requires bun test)\n * polly verify [args] Run formal verification\n * polly visualize [args] Generate architecture diagrams\n * polly help Show help\n *\n * Project Types (init --type):\n * extension Chrome/Firefox extension (default)\n * pwa Progressive Web App with workers\n * websocket WebSocket server application\n * generic Generic TypeScript project\n *\n * Options:\n * --prod Build for production (minified)\n * --config <path> Path to config file (default: polly.config.ts)\n * --fix Auto-fix lint/format issues\n * --type=TYPE Project type for init command\n */\n\n// Use Bun built-ins instead of Node.js APIs\nconst __dirname = import.meta.dir;\n\nconst command = process.argv[2];\nconst commandArgs = process.argv.slice(3);\nconst cwd = process.cwd();\n\n// Parse arguments\nconst args = {\n prod: process.argv.includes(\"--prod\"),\n config: process.argv.includes(\"--config\")\n ? process.argv[process.argv.indexOf(\"--config\") + 1]\n : undefined,\n};\n\n/**\n * Load user's configuration\n */\nasync function loadConfig() {\n const configPaths = [\n args.config,\n `${cwd}/polly.config.ts`,\n `${cwd}/polly.config.js`,\n `${cwd}/polly.config.mjs`,\n ].filter(Boolean) as string[];\n\n for (const configPath of configPaths) {\n // Use Bun.file().exists() instead of existsSync\n if (await Bun.file(configPath).exists()) {\n try {\n const config = await import(configPath);\n return config.default || config;\n } catch (error) {\n console.log(`❌ Failed to load config: ${configPath}`);\n throw error;\n }\n }\n }\n return {\n srcDir: \"src\",\n distDir: \"dist\",\n manifest: \"manifest.json\",\n };\n}\n\n/**\n * Build command - build the extension\n */\nasync function build() {\n const config = await loadConfig();\n\n // Check if bundled (published) or in monorepo\n const bundledScript = `${__dirname}/../scripts/build-extension.js`;\n const monorepoScript = `${__dirname}/../scripts/build-extension.ts`;\n const buildScriptPath = (await Bun.file(bundledScript).exists()) ? bundledScript : monorepoScript;\n\n // Pass config via environment\n process.env[\"WEB_EXT_SRC\"] = `${cwd}/${config.srcDir || \"src\"}`;\n process.env[\"WEB_EXT_DIST\"] = `${cwd}/${config.distDir || \"dist\"}`;\n process.env[\"WEB_EXT_MANIFEST\"] = `${cwd}/${config.manifest || \"manifest.json\"}`;\n process.env[\"WEB_EXT_CWD\"] = cwd;\n process.env[\"WEB_EXT_PROD\"] = args.prod ? \"true\" : \"false\";\n\n // Run build\n const proc = Bun.spawn([\"bun\", buildScriptPath], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n process.exit(exitCode);\n }\n}\n\n/**\n * Dev command - build with watch mode\n */\nasync function dev() {\n await build();\n}\n\n/**\n * Verify command - delegate to @fairfox/web-ext-verify\n */\nasync function verify() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/verify/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/verify/src/cli.ts`;\n const verifyCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", verifyCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Verification failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Visualize command - delegate to @fairfox/polly-visualize\n */\nasync function visualize() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/visualize/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/visualize/src/cli.ts`;\n const visualizeCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", visualizeCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Visualization failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Typecheck command - run TypeScript type checking\n */\nasync function typecheck() {\n const proc = Bun.spawn([\"bunx\", \"tsc\", \"--noEmit\"], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Type checking failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Lint command - run Biome linter\n */\nasync function lint() {\n const fix = commandArgs.includes(\"--fix\");\n const lintArgs = fix ? [\"check\", \"--write\", \".\"] : [\"check\", \".\"];\n\n const proc = Bun.spawn([\"bunx\", \"@biomejs/biome\", ...lintArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Linting failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Format command - run Biome formatter\n */\nasync function format() {\n const proc = Bun.spawn([\"bunx\", \"@biomejs/biome\", \"format\", \"--write\", \".\"], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Formatting failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Test command - delegate to @fairfox/polly-test\n */\nasync function test() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/test/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/test/src/cli.ts`;\n const testCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", testCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Tests failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Check command - run all quality checks in sequence\n */\nasync function check() {\n const checks = [\n { name: \"Type checking\", fn: typecheck },\n { name: \"Linting\", fn: lint },\n { name: \"Testing\", fn: test },\n { name: \"Building\", fn: build },\n { name: \"Verification\", fn: verify, optional: true },\n { name: \"Visualization\", fn: visualize, optional: true },\n ];\n\n for (const { name, fn, optional } of checks) {\n try {\n await fn();\n } catch (_error) {\n if (optional) {\n continue;\n }\n console.log(`\\n\\x1b[31m✗ ${name} failed\\x1b[0m\\n`);\n process.exit(1);\n }\n }\n}\n\n/**\n * Init command - delegate to @fairfox/polly-init\n */\nasync function init() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/init/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/init/src/cli.ts`;\n const initCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", initCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Initialization failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Help command — prints usage from the docstring at the top of this file\n */\nfunction help() {\n console.log(`Polly CLI — multi-execution-context framework\n\nUsage:\n polly init [name] [--type=TYPE] Create a new project\n polly check Run all checks (typecheck, lint, test, build)\n polly build [options] Build the project\n polly dev Build with watch mode\n polly typecheck Type check your code\n polly lint [--fix] Lint your code\n polly format Format your code\n polly test [args] Run tests\n polly verify [args] Run formal verification\n polly visualize [args] Generate architecture diagrams\n polly help Show this help\n\nOptions:\n --prod Build for production (minified)\n --config <path> Path to config file (default: polly.config.ts)\n --fix Auto-fix lint/format issues\n --type=TYPE Project type for init command (pwa, extension, websocket, generic)`);\n}\n\n/**\n * Main entry point\n */\nasync function main() {\n try {\n switch (command) {\n case \"init\":\n await init();\n break;\n case \"check\":\n await check();\n break;\n case \"build\":\n await build();\n break;\n case \"dev\":\n await dev();\n break;\n case \"typecheck\":\n await typecheck();\n break;\n case \"lint\":\n await lint();\n break;\n case \"format\":\n await format();\n break;\n case \"test\":\n await test();\n break;\n case \"verify\":\n await verify();\n break;\n case \"visualize\":\n await visualize();\n break;\n case \"help\":\n case \"--help\":\n case \"-h\":\n case undefined:\n help();\n break;\n default:\n console.log(`❌ Unknown command: ${command}\\n`);\n help();\n process.exit(1);\n }\n } catch (error) {\n console.log(\"\\n❌ Command failed:\", error);\n process.exit(1);\n }\n}\n\nmain();\n"
|
|
5
|
+
"#!/usr/bin/env bun\n/**\n * Polly CLI\n *\n * Command-line tool for building multi-execution-context applications\n * with reactive state and cross-context messaging.\n *\n * Supports: Chrome extensions, PWAs, Node/Bun/Deno apps with workers\n *\n * Usage:\n * polly init [name] [--type=TYPE] Create a new project\n * polly check Run all checks (typecheck, lint, test, build)\n * polly build [options] Build the project\n * polly dev Build with watch mode\n * polly typecheck Type check your code\n * polly lint [--fix] Lint your code\n * polly format Format your code\n * polly test [args] Run tests (requires bun test)\n * polly verify [args] Run formal verification\n * polly visualize [args] Generate architecture diagrams\n * polly quality [args] Run quality conformance checks\n * polly help Show help\n *\n * Project Types (init --type):\n * extension Chrome/Firefox extension (default)\n * pwa Progressive Web App with workers\n * websocket WebSocket server application\n * generic Generic TypeScript project\n *\n * Options:\n * --prod Build for production (minified)\n * --config <path> Path to config file (default: polly.config.ts)\n * --fix Auto-fix lint/format issues\n * --type=TYPE Project type for init command\n */\n\n// Use Bun built-ins instead of Node.js APIs\nconst __dirname = import.meta.dir;\n\nconst command = process.argv[2];\nconst commandArgs = process.argv.slice(3);\nconst cwd = process.cwd();\n\n// Parse arguments\nconst args = {\n prod: process.argv.includes(\"--prod\"),\n config: process.argv.includes(\"--config\")\n ? process.argv[process.argv.indexOf(\"--config\") + 1]\n : undefined,\n};\n\n/**\n * Load user's configuration\n */\nasync function loadConfig() {\n const configPaths = [\n args.config,\n `${cwd}/polly.config.ts`,\n `${cwd}/polly.config.js`,\n `${cwd}/polly.config.mjs`,\n ].filter(Boolean) as string[];\n\n for (const configPath of configPaths) {\n // Use Bun.file().exists() instead of existsSync\n if (await Bun.file(configPath).exists()) {\n try {\n const config = await import(configPath);\n return config.default || config;\n } catch (error) {\n console.log(`❌ Failed to load config: ${configPath}`);\n throw error;\n }\n }\n }\n return {\n srcDir: \"src\",\n distDir: \"dist\",\n manifest: \"manifest.json\",\n };\n}\n\n/**\n * Build command - build the extension\n */\nasync function build() {\n const config = await loadConfig();\n\n // Check if bundled (published) or in monorepo\n const bundledScript = `${__dirname}/../scripts/build-extension.js`;\n const monorepoScript = `${__dirname}/../scripts/build-extension.ts`;\n const buildScriptPath = (await Bun.file(bundledScript).exists()) ? bundledScript : monorepoScript;\n\n // Pass config via environment\n process.env[\"WEB_EXT_SRC\"] = `${cwd}/${config.srcDir || \"src\"}`;\n process.env[\"WEB_EXT_DIST\"] = `${cwd}/${config.distDir || \"dist\"}`;\n process.env[\"WEB_EXT_MANIFEST\"] = `${cwd}/${config.manifest || \"manifest.json\"}`;\n process.env[\"WEB_EXT_CWD\"] = cwd;\n process.env[\"WEB_EXT_PROD\"] = args.prod ? \"true\" : \"false\";\n\n // Run build\n const proc = Bun.spawn([\"bun\", buildScriptPath], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n process.exit(exitCode);\n }\n}\n\n/**\n * Dev command - build with watch mode\n */\nasync function dev() {\n await build();\n}\n\n/**\n * Verify command - delegate to @fairfox/web-ext-verify\n */\nasync function verify() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/verify/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/verify/src/cli.ts`;\n const verifyCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", verifyCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Verification failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Visualize command - delegate to @fairfox/polly-visualize\n */\nasync function visualize() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/visualize/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/visualize/src/cli.ts`;\n const visualizeCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", visualizeCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Visualization failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Quality command - delegate to @fairfox/polly-quality\n */\nasync function quality() {\n const bundledCli = `${__dirname}/../tools/quality/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/quality/src/cli.ts`;\n const qualityCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", qualityCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n env: process.env,\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Quality checks failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Typecheck command - run TypeScript type checking\n */\nasync function typecheck() {\n const proc = Bun.spawn([\"bunx\", \"tsc\", \"--noEmit\"], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Type checking failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Lint command - run Biome linter\n */\nasync function lint() {\n const fix = commandArgs.includes(\"--fix\");\n const lintArgs = fix ? [\"check\", \"--write\", \".\"] : [\"check\", \".\"];\n\n const proc = Bun.spawn([\"bunx\", \"@biomejs/biome\", ...lintArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Linting failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Format command - run Biome formatter\n */\nasync function format() {\n const proc = Bun.spawn([\"bunx\", \"@biomejs/biome\", \"format\", \"--write\", \".\"], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Formatting failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Test command - delegate to @fairfox/polly-test\n */\nasync function test() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/test/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/test/src/cli.ts`;\n const testCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", testCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Tests failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Check command - run all quality checks in sequence\n */\nasync function check() {\n const checks = [\n { name: \"Type checking\", fn: typecheck },\n { name: \"Linting\", fn: lint },\n { name: \"Testing\", fn: test },\n { name: \"Building\", fn: build },\n { name: \"Verification\", fn: verify, optional: true },\n { name: \"Visualization\", fn: visualize, optional: true },\n ];\n\n for (const { name, fn, optional } of checks) {\n try {\n await fn();\n } catch (_error) {\n if (optional) {\n continue;\n }\n console.log(`\\n\\x1b[31m✗ ${name} failed\\x1b[0m\\n`);\n process.exit(1);\n }\n }\n}\n\n/**\n * Init command - delegate to @fairfox/polly-init\n */\nasync function init() {\n // Check if bundled (published) or in monorepo\n const bundledCli = `${__dirname}/../tools/init/src/cli.js`;\n const monorepoCli = `${__dirname}/../tools/init/src/cli.ts`;\n const initCli = (await Bun.file(bundledCli).exists()) ? bundledCli : monorepoCli;\n\n const proc = Bun.spawn([\"bun\", initCli, ...commandArgs], {\n cwd,\n stdout: \"inherit\",\n stderr: \"inherit\",\n stdin: \"inherit\",\n });\n\n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n throw new Error(`Initialization failed with exit code ${exitCode}`);\n }\n}\n\n/**\n * Help command — prints usage from the docstring at the top of this file\n */\nfunction help() {\n console.log(`Polly CLI — multi-execution-context framework\n\nUsage:\n polly init [name] [--type=TYPE] Create a new project\n polly check Run all checks (typecheck, lint, test, build)\n polly build [options] Build the project\n polly dev Build with watch mode\n polly typecheck Type check your code\n polly lint [--fix] Lint your code\n polly format Format your code\n polly test [args] Run tests\n polly verify [args] Run formal verification\n polly visualize [args] Generate architecture diagrams\n polly quality [args] Run quality conformance checks\n polly help Show this help\n\nOptions:\n --prod Build for production (minified)\n --config <path> Path to config file (default: polly.config.ts)\n --fix Auto-fix lint/format issues\n --type=TYPE Project type for init command (pwa, extension, websocket, generic)`);\n}\n\n/**\n * Main entry point\n */\nasync function main() {\n try {\n switch (command) {\n case \"init\":\n await init();\n break;\n case \"check\":\n await check();\n break;\n case \"build\":\n await build();\n break;\n case \"dev\":\n await dev();\n break;\n case \"typecheck\":\n await typecheck();\n break;\n case \"lint\":\n await lint();\n break;\n case \"format\":\n await format();\n break;\n case \"test\":\n await test();\n break;\n case \"verify\":\n await verify();\n break;\n case \"visualize\":\n await visualize();\n break;\n case \"quality\":\n await quality();\n break;\n case \"help\":\n case \"--help\":\n case \"-h\":\n case undefined:\n help();\n break;\n default:\n console.log(`❌ Unknown command: ${command}\\n`);\n help();\n process.exit(1);\n }\n } catch (error) {\n console.log(\"\\n❌ Command failed:\", error);\n process.exit(1);\n }\n}\n\nmain();\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "B9B6663515E3E39364756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/src/elysia/index.js
CHANGED
|
@@ -78,7 +78,9 @@ async function createPeerRepoServer(options) {
|
|
|
78
78
|
client.terminate();
|
|
79
79
|
} catch {}
|
|
80
80
|
}
|
|
81
|
-
|
|
81
|
+
try {
|
|
82
|
+
await repo.shutdown();
|
|
83
|
+
} catch {}
|
|
82
84
|
try {
|
|
83
85
|
wss.close();
|
|
84
86
|
} catch {}
|
|
@@ -394,4 +396,4 @@ export {
|
|
|
394
396
|
peerRepo
|
|
395
397
|
};
|
|
396
398
|
|
|
397
|
-
//# debugId=
|
|
399
|
+
//# debugId=93C98D54564BCF4764756E2164756E21
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"sources": ["../src/elysia/peer-repo-plugin.ts", "../src/shared/lib/peer-repo-server.ts", "../src/elysia/plugin.ts", "../src/core/clock.ts", "../src/utils/function-serialization.ts", "../src/elysia/signaling-server-plugin.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"// @ts-nocheck - Optional peer dependencies (elysia, @automerge/automerge-repo*)\n/**\n * peerRepo — Elysia plugin that runs a Polly peer-relay server alongside an\n * Elysia application and exposes the server's Repo on the Elysia context.\n *\n * The Phase 1 plan originally framed the relay server as an Elysia plugin\n * that registers a WebSocket route on Elysia's native socket layer. That\n * design does not work directly: Automerge's WebSocketServerAdapter requires\n * an `isomorphic-ws` WebSocketServer instance, and Elysia's WebSocket layer\n * is built on Bun's native WebSocket API, which is a different shape. The\n * realistic Phase 1 cut is therefore a *lifecycle* plugin: it runs\n * createPeerRepoServer on its own port during Elysia startup, decorates the\n * Elysia context so route handlers can reach the resulting Repo, and tears\n * the server down during Elysia shutdown. Authenticated WebSocket upgrades\n * via Elysia's hook system are a Phase 1.1 follow-up that requires either\n * a custom Bun-native NetworkAdapter or an upgrade-time auth bridge.\n *\n * @example\n * ```ts\n * import { Elysia } from \"elysia\";\n * import { peerRepo } from \"@fairfox/polly/elysia\";\n *\n * const app = new Elysia()\n * .use(peerRepo({ port: 3030, storagePath: \"./data/polly-peer\" }))\n * .get(\"/stats\", ({ pollyPeerRepo }) => {\n * return { peers: pollyPeerRepo.peers.length };\n * })\n * .listen(8080);\n * ```\n */\n\nimport { Elysia } from \"elysia\";\nimport {\n type CreatePeerRepoServerOptions,\n createPeerRepoServer,\n type PeerRepoServer,\n} from \"../shared/lib/peer-repo-server\";\n\nexport interface PeerRepoPluginOptions extends CreatePeerRepoServerOptions {\n /** Optional path for a health endpoint that returns peer count and storage\n * id. Set to false to skip the health route entirely. Defaults to\n * \"/polly/peer/health\". */\n healthPath?: string | false;\n}\n\n/**\n * Elysia plugin that boots a Polly peer-relay server alongside the host app\n * and decorates the context with `pollyPeerRepo` (the Repo) and\n * `pollyPeerServer` (the full {@link PeerRepoServer} for advanced use).\n *\n * The plugin starts the relay server during Elysia's onStart lifecycle and\n * shuts it down during onStop, so route handlers and cron jobs can use the\n * decorated Repo throughout the request lifetime without managing the\n * underlying transport themselves.\n */\nexport function peerRepo(options: PeerRepoPluginOptions) {\n // The plugin holds a single PeerRepoServer for the lifetime of the Elysia\n // app. It is created during onStart so the listening socket is bound and\n // ready before any request handler can reach it.\n let server: PeerRepoServer | undefined;\n\n const healthPath =\n options.healthPath === false ? null : (options.healthPath ?? \"/polly/peer/health\");\n\n let app = new Elysia({ name: \"polly-peer-repo\" })\n .decorate(\"pollyPeerRepo\", null as unknown as PeerRepoServer[\"repo\"] | null)\n .decorate(\"pollyPeerServer\", null as unknown as PeerRepoServer | null)\n .onStart(async () => {\n server = await createPeerRepoServer(options);\n // Decorate after construction so handlers see the live Repo. Elysia's\n // decorate is mutable in-place when called on the running instance.\n app.decorate(\"pollyPeerRepo\", server.repo);\n app.decorate(\"pollyPeerServer\", server);\n })\n .onStop(async () => {\n if (server) {\n await server.close();\n server = undefined;\n }\n });\n\n if (healthPath) {\n app = app.get(healthPath, () => {\n if (!server) {\n return { status: \"starting\", peers: 0 };\n }\n return {\n status: \"running\",\n peers: server.repo.peers.length,\n port: options.port,\n };\n });\n }\n\n return app;\n}\n",
|
|
6
|
-
"/**\n * peer-repo-server — Phase 1 server-side factory for the Polly peer-relay\n * transport. Constructs an Automerge-Repo `Repo` wired to a WebSocket server\n * and a NodeFS storage backend, ready to relay sync messages between\n * connected $peerState clients.\n *\n * The \"always-on peer\" role for $peerState lives here. The server holds a\n * full Automerge replica of every document, participates in the sync protocol\n * as an ordinary peer, and persists state to disk so the next process restart\n * picks up where the previous one left off. Server-side cron, HTTP handlers,\n * and other compute can open document handles on the returned Repo and mutate\n * them; mutations propagate to connected clients through the same sync\n * protocol that handles client-to-client traffic.\n *\n * The plan originally called this an \"Elysia plugin,\" but Automerge's\n * `WebSocketServerAdapter` requires an `isomorphic-ws` `WebSocketServer`\n * instance — not Elysia's native WebSocket — so the cleanest first cut is a\n * standalone factory that runs its own `ws` server. Elysia integration for\n * authenticated upgrades is a Phase 1.1 follow-up that wraps this factory.\n *\n * @example\n * ```ts\n * import { createPeerRepoServer } from \"@fairfox/polly\";\n *\n * const server = await createPeerRepoServer({\n * port: 3030,\n * storagePath: \"./data/polly-peer\",\n * });\n *\n * // Open a document handle on the server's Repo for cron or compute work.\n * const handle = server.repo.create({ counter: 0 });\n *\n * // On shutdown:\n * await server.close();\n * ```\n */\n\nimport { Repo } from \"@automerge/automerge-repo\";\nimport { WebSocketServerAdapter } from \"@automerge/automerge-repo-network-websocket\";\nimport { NodeFSStorageAdapter } from \"@automerge/automerge-repo-storage-nodefs\";\nimport * as ws from \"ws\";\n\n// `@types/ws` uses CJS `export = WebSocket` with WebSocketServer hanging off\n// the namespace. Under the project's bundler module resolution, the namespace\n// import gives us access to both the constructor and the type.\ntype WebSocketServer = ws.WebSocketServer;\n\nexport interface CreatePeerRepoServerOptions {\n /** Port to listen on. The factory creates its own `WebSocketServer` and\n * binds to this port. */\n port: number;\n /** Filesystem directory for the NodeFS storage adapter. The directory is\n * created on demand. Defaults to `./automerge-repo-data` (Automerge's own\n * default). */\n storagePath?: string;\n /** Hostname interface to bind to. Defaults to all interfaces. */\n host?: string;\n /** Override the `WebSocketServer` instance entirely. When provided, `port`\n * and `host` are ignored and the caller is responsible for the lifecycle.\n * Useful for tests that want to bind to a random port. */\n webSocketServer?: WebSocketServer;\n}\n\nexport interface PeerRepoServer {\n /** A configured Repo participating as the always-on peer. Server-side\n * cron and HTTP handlers can open document handles on this directly. */\n repo: Repo;\n /** The underlying WebSocket server. Exposed for advanced use such as\n * health checks or graceful shutdown coordination. */\n webSocketServer: WebSocketServer;\n /** The Automerge network adapter wrapping the WebSocket server. */\n adapter: WebSocketServerAdapter;\n /** The NodeFS storage adapter writing to {@link CreatePeerRepoServerOptions.storagePath}. */\n storage: NodeFSStorageAdapter;\n /** Tear down the server: disconnect peers, flush storage, close the\n * underlying WebSocket server. Returns a promise that resolves once the\n * tear-down is complete. */\n close: () => Promise<void>;\n}\n\n/**\n * Construct a Polly peer-relay server. Returns a Repo that participates as\n * the always-on peer, the underlying WebSocket server and storage adapter\n * for advanced use, and a close function for orderly shutdown.\n *\n * Applications typically call this once at startup, hold the returned\n * `repo` reference for cron and compute work, and wire the close function\n * into their process shutdown signal handlers.\n */\nexport async function createPeerRepoServer(\n options: CreatePeerRepoServerOptions\n): Promise<PeerRepoServer> {\n // Construct the WebSocket server first and wait until it is actually\n // listening before wiring up the Repo. Using the constructor callback\n // avoids the race where the 'listening' event fires before our listener\n // is attached (the callback form is reliable across Node and Bun).\n const wss = await (options.webSocketServer\n ? Promise.resolve(options.webSocketServer)\n : new Promise<WebSocketServer>((resolve, reject) => {\n const created: WebSocketServer = new ws.WebSocketServer(\n {\n port: options.port,\n ...(options.host !== undefined && { host: options.host }),\n },\n () => resolve(created)\n );\n created.once(\"error\", reject);\n }));\n\n // The cast bridges a @types/ws identity quirk: Automerge's adapter type\n // expects WebSocketServer with options.WebSocket typed via isomorphic-ws's\n // CJS-style namespace, and our direct `ws` import resolves through a\n // different path with the same runtime shape but a structurally distinct\n // TypeScript type. The runtime is identical, the cast names that fact.\n const adapter = new WebSocketServerAdapter(\n wss as unknown as ConstructorParameters<typeof WebSocketServerAdapter>[0]\n );\n const storage = new NodeFSStorageAdapter(options.storagePath);\n\n const repo = new Repo({\n network: [adapter],\n storage,\n });\n\n // Force the storage subsystem to finish initialising before returning. The\n // Repo constructor is synchronous, but its NetworkSubsystem holds back the\n // peer-metadata JOIN until storageSubsystem.id() resolves. If a client\n // connects before that resolution lands, the handshake stalls indefinitely.\n // Awaiting storageId() drains the relevant microtask chain and guarantees\n // the server is ready to accept peers when this factory returns.\n await repo.storageId();\n\n return {\n repo,\n webSocketServer: wss,\n adapter,\n storage,\n close: async () => {\n // Forcibly terminate any still-open client sockets before closing the\n // server, otherwise wss.close() can hang waiting for orderly drain when\n // a peer disappeared without a clean disconnect. We then fire the\n // server close without awaiting its callback — the underlying socket\n // is released immediately by terminate(), and waiting for the close\n // callback can hang under Bun even after every client is gone.\n for (const client of wss.clients) {\n try {\n client.terminate();\n } catch {\n // best effort\n }\n }\n
|
|
6
|
+
"/**\n * peer-repo-server — Phase 1 server-side factory for the Polly peer-relay\n * transport. Constructs an Automerge-Repo `Repo` wired to a WebSocket server\n * and a NodeFS storage backend, ready to relay sync messages between\n * connected $peerState clients.\n *\n * The \"always-on peer\" role for $peerState lives here. The server holds a\n * full Automerge replica of every document, participates in the sync protocol\n * as an ordinary peer, and persists state to disk so the next process restart\n * picks up where the previous one left off. Server-side cron, HTTP handlers,\n * and other compute can open document handles on the returned Repo and mutate\n * them; mutations propagate to connected clients through the same sync\n * protocol that handles client-to-client traffic.\n *\n * The plan originally called this an \"Elysia plugin,\" but Automerge's\n * `WebSocketServerAdapter` requires an `isomorphic-ws` `WebSocketServer`\n * instance — not Elysia's native WebSocket — so the cleanest first cut is a\n * standalone factory that runs its own `ws` server. Elysia integration for\n * authenticated upgrades is a Phase 1.1 follow-up that wraps this factory.\n *\n * @example\n * ```ts\n * import { createPeerRepoServer } from \"@fairfox/polly\";\n *\n * const server = await createPeerRepoServer({\n * port: 3030,\n * storagePath: \"./data/polly-peer\",\n * });\n *\n * // Open a document handle on the server's Repo for cron or compute work.\n * const handle = server.repo.create({ counter: 0 });\n *\n * // On shutdown:\n * await server.close();\n * ```\n */\n\nimport { Repo } from \"@automerge/automerge-repo\";\nimport { WebSocketServerAdapter } from \"@automerge/automerge-repo-network-websocket\";\nimport { NodeFSStorageAdapter } from \"@automerge/automerge-repo-storage-nodefs\";\nimport * as ws from \"ws\";\n\n// `@types/ws` uses CJS `export = WebSocket` with WebSocketServer hanging off\n// the namespace. Under the project's bundler module resolution, the namespace\n// import gives us access to both the constructor and the type.\ntype WebSocketServer = ws.WebSocketServer;\n\nexport interface CreatePeerRepoServerOptions {\n /** Port to listen on. The factory creates its own `WebSocketServer` and\n * binds to this port. */\n port: number;\n /** Filesystem directory for the NodeFS storage adapter. The directory is\n * created on demand. Defaults to `./automerge-repo-data` (Automerge's own\n * default). */\n storagePath?: string;\n /** Hostname interface to bind to. Defaults to all interfaces. */\n host?: string;\n /** Override the `WebSocketServer` instance entirely. When provided, `port`\n * and `host` are ignored and the caller is responsible for the lifecycle.\n * Useful for tests that want to bind to a random port. */\n webSocketServer?: WebSocketServer;\n}\n\nexport interface PeerRepoServer {\n /** A configured Repo participating as the always-on peer. Server-side\n * cron and HTTP handlers can open document handles on this directly. */\n repo: Repo;\n /** The underlying WebSocket server. Exposed for advanced use such as\n * health checks or graceful shutdown coordination. */\n webSocketServer: WebSocketServer;\n /** The Automerge network adapter wrapping the WebSocket server. */\n adapter: WebSocketServerAdapter;\n /** The NodeFS storage adapter writing to {@link CreatePeerRepoServerOptions.storagePath}. */\n storage: NodeFSStorageAdapter;\n /** Tear down the server: disconnect peers, flush storage, close the\n * underlying WebSocket server. Returns a promise that resolves once the\n * tear-down is complete. */\n close: () => Promise<void>;\n}\n\n/**\n * Construct a Polly peer-relay server. Returns a Repo that participates as\n * the always-on peer, the underlying WebSocket server and storage adapter\n * for advanced use, and a close function for orderly shutdown.\n *\n * Applications typically call this once at startup, hold the returned\n * `repo` reference for cron and compute work, and wire the close function\n * into their process shutdown signal handlers.\n */\nexport async function createPeerRepoServer(\n options: CreatePeerRepoServerOptions\n): Promise<PeerRepoServer> {\n // Construct the WebSocket server first and wait until it is actually\n // listening before wiring up the Repo. Using the constructor callback\n // avoids the race where the 'listening' event fires before our listener\n // is attached (the callback form is reliable across Node and Bun).\n const wss = await (options.webSocketServer\n ? Promise.resolve(options.webSocketServer)\n : new Promise<WebSocketServer>((resolve, reject) => {\n const created: WebSocketServer = new ws.WebSocketServer(\n {\n port: options.port,\n ...(options.host !== undefined && { host: options.host }),\n },\n () => resolve(created)\n );\n created.once(\"error\", reject);\n }));\n\n // The cast bridges a @types/ws identity quirk: Automerge's adapter type\n // expects WebSocketServer with options.WebSocket typed via isomorphic-ws's\n // CJS-style namespace, and our direct `ws` import resolves through a\n // different path with the same runtime shape but a structurally distinct\n // TypeScript type. The runtime is identical, the cast names that fact.\n const adapter = new WebSocketServerAdapter(\n wss as unknown as ConstructorParameters<typeof WebSocketServerAdapter>[0]\n );\n const storage = new NodeFSStorageAdapter(options.storagePath);\n\n const repo = new Repo({\n network: [adapter],\n storage,\n });\n\n // Force the storage subsystem to finish initialising before returning. The\n // Repo constructor is synchronous, but its NetworkSubsystem holds back the\n // peer-metadata JOIN until storageSubsystem.id() resolves. If a client\n // connects before that resolution lands, the handshake stalls indefinitely.\n // Awaiting storageId() drains the relevant microtask chain and guarantees\n // the server is ready to accept peers when this factory returns.\n await repo.storageId();\n\n return {\n repo,\n webSocketServer: wss,\n adapter,\n storage,\n close: async () => {\n // Forcibly terminate any still-open client sockets before closing the\n // server, otherwise wss.close() can hang waiting for orderly drain when\n // a peer disappeared without a clean disconnect. We then fire the\n // server close without awaiting its callback — the underlying socket\n // is released immediately by terminate(), and waiting for the close\n // callback can hang under Bun even after every client is gone.\n for (const client of wss.clients) {\n try {\n client.terminate();\n } catch {\n // best effort\n }\n }\n try {\n await repo.shutdown();\n } catch {\n // best effort — automerge-repo's xstate DocHandle machine can\n // throw \"Cycle detected\" during teardown when sync messages are\n // still in flight.\n }\n try {\n wss.close();\n } catch {\n // best effort\n }\n },\n };\n}\n",
|
|
7
7
|
"// @ts-nocheck - Optional peer dependencies (elysia, @elysiajs/eden)\nimport type { Signal } from \"@preact/signals-core\";\nimport { Elysia } from \"elysia\";\nimport { createLamportClock } from \"../core/clock\";\nimport { serializeFunction } from \"../utils/function-serialization\";\nimport type { PollyConfig, PollyResponseMetadata } from \"./types\";\n\n/**\n * Broadcast message sent to connected clients\n */\ninterface BroadcastMessage {\n type: \"effect\";\n path: string;\n method: string;\n result: unknown;\n clock: { tick: number; contextId: string };\n}\n\n/**\n * Minimal WebSocket interface for broadcasting\n */\ninterface MinimalWebSocket {\n readyState: number;\n send(data: string): void;\n}\n\n/**\n * Route pattern matcher\n * Supports:\n * - Exact match: 'POST /todos'\n * - Param match: 'GET /todos/:id'\n * - Wildcard: '/todos/*'\n */\nfunction matchRoute(pattern: string, method: string, path: string): boolean {\n // Split pattern into method + path or just path\n const hasMethod = pattern.includes(\" \");\n const patternMethod = hasMethod ? pattern.split(\" \")[0] : null;\n const patternPath = hasMethod ? pattern.split(\" \")[1] : pattern;\n\n // Check method\n if (patternMethod && patternMethod !== method) {\n return false;\n }\n\n // Check path\n const patternSegments = patternPath.split(\"/\").filter(Boolean);\n const pathSegments = path.split(\"/\").filter(Boolean);\n\n if (patternSegments.length !== pathSegments.length && !patternPath.includes(\"*\")) {\n return false;\n }\n\n for (let i = 0; i < patternSegments.length; i++) {\n const patternSeg = patternSegments[i];\n const pathSeg = pathSegments[i];\n\n if (patternSeg === \"*\") return true;\n if (patternSeg.startsWith(\":\")) continue; // Param match\n if (patternSeg !== pathSeg) return false;\n }\n\n return true;\n}\n\n/**\n * Find matching config for a route\n */\nfunction findMatchingConfig<T>(\n configs: Record<string, T> | undefined,\n method: string,\n path: string\n): T | undefined {\n if (!configs) return undefined;\n\n for (const [pattern, config] of Object.entries(configs)) {\n if (matchRoute(pattern, method, path)) {\n return config;\n }\n }\n\n return undefined;\n}\n\n/**\n * WebSocket broadcast manager\n */\nclass BroadcastManager {\n private connections = new Map<string, MinimalWebSocket>();\n\n register(clientId: string, ws: MinimalWebSocket) {\n this.connections.set(clientId, ws);\n }\n\n unregister(clientId: string) {\n this.connections.delete(clientId);\n }\n\n broadcast(message: BroadcastMessage, filter?: (clientId: string) => boolean) {\n const payload = JSON.stringify(message);\n\n for (const [clientId, ws] of this.connections.entries()) {\n if (filter && !filter(clientId)) continue;\n if (ws.readyState === 1) {\n // WebSocket.OPEN = 1\n ws.send(payload);\n }\n }\n }\n}\n\n/**\n * Main Polly Elysia plugin\n */\nexport function polly(config: PollyConfig = {}) {\n const isDev = process.env.NODE_ENV !== \"production\";\n const clock = createLamportClock(\"server\");\n const broadcaster = new BroadcastManager();\n const clientStateByConnection = new Map<string, Record<string, Signal<unknown>>>();\n\n const app = new Elysia({ name: \"polly\" })\n // Add state to context\n .decorate(\"pollyState\", {\n client: config.state?.client || {},\n server: config.state?.server || {},\n })\n .decorate(\"pollyClock\", clock)\n .decorate(\"pollyBroadcast\", broadcaster)\n\n // WebSocket endpoint for real-time updates\n .ws(config.websocketPath || \"/polly/ws\", {\n // @ts-expect-error - Elysia WebSocket types from optional peer dependency\n open(ws) {\n const clientId = ws.data.headers?.[\"x-client-id\"] || crypto.randomUUID();\n broadcaster.register(clientId, ws.raw);\n\n // Send initial state sync\n ws.send(\n JSON.stringify({\n type: \"state-sync\",\n state: config.state?.client || {},\n clock: clock.now(),\n })\n );\n },\n // @ts-expect-error - Elysia WebSocket types from optional peer dependency\n close(ws) {\n const clientId = ws.data.headers?.[\"x-client-id\"];\n if (clientId) {\n broadcaster.unregister(clientId);\n clientStateByConnection.delete(clientId);\n }\n },\n // @ts-expect-error - Elysia WebSocket types from optional peer dependency\n message(ws, message) {\n // Handle client state updates\n const data = JSON.parse(message as unknown as string);\n\n if (data.type === \"state-update\") {\n const clientId = ws.data.headers?.[\"x-client-id\"];\n if (clientId) {\n clientStateByConnection.set(clientId, data.state);\n }\n }\n },\n })\n\n // Authorization hook (runs before handler)\n // @ts-expect-error - Elysia context types from optional peer dependency\n .onBeforeHandle(async ({ request, pollyState, body, params }) => {\n const method = request.method;\n const path = new URL(request.url).pathname;\n\n const authHandler = findMatchingConfig(config.authorization, method, path);\n\n if (authHandler) {\n const allowed = await authHandler({\n state: pollyState,\n body,\n params,\n headers: Object.fromEntries(request.headers.entries()),\n });\n\n if (!allowed) {\n return new Response(\"Unauthorized\", { status: 403 });\n }\n }\n })\n\n // Response hook (runs after handler)\n // @ts-expect-error - Elysia context types from optional peer dependency\n .onAfterHandle(\n async ({ request, response, _pollyState, pollyClock, pollyBroadcast, _body, _params }) => {\n const method = request.method;\n const path = new URL(request.url).pathname;\n\n // Find matching effect config\n const effectConfig = findMatchingConfig(config.effects, method, path);\n\n // Tick clock\n pollyClock.tick();\n\n // If broadcast enabled, send to all connected clients\n // This works in both dev and prod for real-time updates\n if (effectConfig?.broadcast) {\n const broadcastMessage = {\n type: \"effect\",\n path,\n method,\n result: response,\n clock: pollyClock.now(),\n };\n\n if (effectConfig.broadcastFilter) {\n pollyBroadcast.broadcast(broadcastMessage, (clientId) => {\n const clientState = clientStateByConnection.get(clientId) || {};\n return effectConfig.broadcastFilter?.(clientState) ?? false;\n });\n } else {\n pollyBroadcast.broadcast(broadcastMessage);\n }\n }\n\n // In production, skip expensive metadata operations\n if (!isDev) {\n return response;\n }\n\n // DEV ONLY: Add Polly metadata to response for debugging/hot-reload\n const offlineConfig = findMatchingConfig(config.offline, method, path);\n const metadata: PollyResponseMetadata = {\n clientEffect: effectConfig\n ? {\n handler: serializeFunction(effectConfig.client),\n broadcast: effectConfig.broadcast || false,\n }\n : undefined,\n offline: offlineConfig,\n clock: pollyClock.now(),\n };\n\n // Attach metadata as header\n return new Response(JSON.stringify(response), {\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Polly-Metadata\": JSON.stringify(metadata),\n },\n });\n }\n );\n\n return app;\n}\n",
|
|
8
8
|
"/**\n * Lamport Clock Implementation\n *\n * Provides logical timestamps for distributed systems to establish\n * causal ordering of events across different contexts (client/server).\n *\n * Key properties:\n * - Each event increments the local clock\n * - When receiving a message, clock = max(local, received) + 1\n * - If A happens before B, then timestamp(A) < timestamp(B)\n *\n * References:\n * - Lamport, L. (1978). \"Time, Clocks, and the Ordering of Events in a Distributed System\"\n * - https://lamport.azurewebsites.net/pubs/time-clocks.pdf\n */\n\n/**\n * Lamport clock state\n */\nexport interface LamportClock {\n tick: number;\n contextId: string;\n}\n\n/**\n * Lamport clock with operations\n */\nexport interface LamportClockOps {\n /**\n * Get current clock value\n */\n now(): LamportClock;\n\n /**\n * Increment the clock (before sending a message or performing an action)\n */\n tick(): number;\n\n /**\n * Update clock when receiving a message\n * Sets clock to max(local, received) + 1\n */\n update(receivedClock: LamportClock): void;\n}\n\n/**\n * Create a Lamport clock for a specific context\n *\n * @param contextId - Unique identifier for this context (e.g., \"client\", \"server\", \"worker-1\")\n * @returns Clock operations\n *\n * @example\n * ```typescript\n * const serverClock = createLamportClock(\"server\");\n *\n * // Before sending a message\n * serverClock.tick();\n * const timestamp = serverClock.now();\n * send({ data: \"...\", clock: timestamp });\n *\n * // When receiving a message\n * onReceive((message) => {\n * serverClock.update(message.clock);\n * // Process message with updated clock\n * });\n * ```\n */\nexport function createLamportClock(contextId: string): LamportClockOps {\n let tick = 0;\n\n return {\n now(): LamportClock {\n return { tick, contextId };\n },\n\n tick(): number {\n tick += 1;\n return tick;\n },\n\n update(receivedClock: LamportClock): void {\n tick = Math.max(tick, receivedClock.tick) + 1;\n },\n };\n}\n",
|
|
9
9
|
"import serialize from \"serialize-javascript\";\n\n/**\n * Check if we're in development mode\n */\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * Serialize a function to send to client\n *\n * DEV ONLY: Used for hot reloading and debugging\n * PROD: No-op - client effects are baked into bundle at build time\n */\n// biome-ignore lint/complexity/noBannedTypes: Generic function serialization requires Function type\nexport function serializeFunction(fn: Function): string {\n if (!isDev) {\n // In production, return empty string - this won't be used\n return \"\";\n }\n\n return serialize(fn, { space: 0 });\n}\n\n/**\n * Deserialize a function received from server\n *\n * DEV ONLY: Eval serialized function source\n * PROD: Should never be called - effects come from bundle\n */\n// biome-ignore lint/complexity/noBannedTypes: Generic function deserialization requires Function type\nexport function deserializeFunction(serialized: string): Function {\n if (!isDev) {\n throw new Error(\n \"[Polly] deserializeFunction should not be called in production. \" +\n \"Client effects should be imported from your bundle.\"\n );\n }\n\n if (!serialized) {\n throw new Error(\"[Polly] Cannot deserialize empty function\");\n }\n\n // biome-ignore lint/security/noGlobalEval: Required for dev-mode function deserialization\n return eval(`(${serialized})`);\n}\n",
|
|
10
10
|
"// @ts-nocheck - Optional peer dependencies (elysia, @elysiajs/eden)\n/**\n * signalingServer — Phase 2 Elysia plugin that exposes a stateless\n * WebSocket route for SDP/ICE relay between $meshState peers.\n *\n * The mesh transport is a star-of-data-channels: peers establish direct\n * WebRTC connections to each other and exchange document operations\n * peer-to-peer once those channels are open. WebRTC connection setup\n * needs an out-of-band channel for SDP offer/answer and ICE candidate\n * exchange, and that channel is what this plugin provides. The plugin\n * does not own any document state, does not hold any encryption keys,\n * and never inspects the contents of the messages it relays. It is a\n * pure pub-sub by peer id.\n *\n * Once two peers have completed the SDP exchange and opened a direct\n * data channel, the signalling server is no longer on the critical\n * path — the peers talk directly. The signalling server's role is\n * therefore intermittent: peers connect to it only during the brief\n * windows when they are establishing or re-establishing connections.\n *\n * Wire protocol:\n *\n * Client → server (join):\n * { type: \"join\", peerId: \"peer-alice\" }\n *\n * Client → server (signal to another peer):\n * { type: \"signal\", peerId: \"peer-alice\", targetPeerId: \"peer-bob\",\n * payload: { ... } }\n *\n * Server → client (delivered signal):\n * { type: \"signal\", peerId: \"peer-alice\", targetPeerId: \"peer-bob\",\n * payload: { ... } }\n *\n * Server → client (notification of unknown target):\n * { type: \"error\", reason: \"unknown-target\", targetPeerId: \"...\" }\n *\n * The `payload` is opaque to the signalling server — typically it\n * carries an SDP offer, SDP answer, or ICE candidate. Applications can\n * also use the relay for any other peer-to-peer message that needs an\n * intermediary, such as the initial handshake of a pairing flow.\n *\n * @example\n * ```ts\n * import { Elysia } from \"elysia\";\n * import { signalingServer } from \"@fairfox/polly/elysia\";\n *\n * const app = new Elysia()\n * .use(signalingServer({ path: \"/polly/signaling\" }))\n * .listen(8080);\n * ```\n */\n\nimport { Elysia } from \"elysia\";\n\n/** A signalling message. The `type` discriminates between join (peer\n * registration), signal (relayed message), and error (server response). */\nexport type SignalingMessage =\n | {\n type: \"join\";\n /** The peer registering itself with the signalling server. */\n peerId: string;\n }\n | {\n type: \"signal\";\n /** The peer sending the signal. */\n peerId: string;\n /** The peer the signal is being relayed to. */\n targetPeerId: string;\n /** Opaque payload, typically SDP or ICE. */\n payload: unknown;\n }\n | {\n type: \"error\";\n reason: \"unknown-target\" | \"not-joined\" | \"malformed\";\n targetPeerId?: string;\n };\n\nexport interface SignalingServerOptions {\n /** WebSocket route path. Defaults to \"/polly/signaling\". */\n path?: string;\n}\n\n/**\n * Construct the signalling-server Elysia plugin. The plugin keeps a\n * per-instance map of peer id → WebSocket connection so that incoming\n * \"signal\" messages can be routed to the right target socket. The map\n * is local to the plugin instance, not shared across processes; for\n * multi-instance deployments behind a load balancer, applications need\n * sticky connection routing or a shared backplane (Redis pub-sub or\n * similar). That is a follow-up.\n */\nexport function signalingServer(options: SignalingServerOptions = {}) {\n const path = options.path ?? \"/polly/signaling\";\n // Per-peer-id map of joined sockets. The inverse mapping is stored\n // directly on ws.data (a mutable property bag that Elysia preserves\n // across message callbacks for a given connection); the webrtc-p2p-chat\n // example in examples/ confirms this pattern is stable under Bun.\n const peerSockets = new Map<string, { send: (msg: unknown) => void }>();\n\n // Intentionally unnamed — Elysia deduplicates plugins by name, and each\n // signalingServer() call needs its own closure-captured peer map.\n const parseMessage = (raw: unknown): SignalingMessage | undefined => {\n try {\n return typeof raw === \"string\" ? JSON.parse(raw) : (raw as unknown as SignalingMessage);\n } catch {\n return undefined;\n }\n };\n\n const handleJoin = (ws: unknown, peerId: string): void => {\n peerSockets.set(peerId, ws as unknown as { send: (m: unknown) => void });\n const wsWithData = ws as unknown as { data: Record<string, unknown> };\n wsWithData.data.peerId = peerId;\n };\n\n const sendUnknownTarget = (ws: unknown, targetPeerId: string): void => {\n (ws as unknown as { send: (m: unknown) => void }).send({\n type: \"error\",\n reason: \"unknown-target\",\n targetPeerId,\n } as unknown as SignalingMessage);\n };\n\n /** Look up a target socket and confirm it is still open. */\n const findOpenTarget = (targetPeerId: string): { send: (msg: unknown) => void } | undefined => {\n const target = peerSockets.get(targetPeerId);\n if (!target) return undefined;\n const readyState = (target as unknown as { readyState?: number }).readyState;\n const OPEN = 1;\n if (readyState !== undefined && readyState !== OPEN) {\n peerSockets.delete(targetPeerId);\n return undefined;\n }\n return target;\n };\n\n const handleSignal = (ws: unknown, msg: Extract<SignalingMessage, { type: \"signal\" }>): void => {\n const wsWithData = ws as unknown as {\n data: Record<string, unknown>;\n send: (m: unknown) => void;\n };\n const senderId = wsWithData.data.peerId as unknown as string | undefined;\n if (!senderId) {\n wsWithData.send({ type: \"error\", reason: \"not-joined\" } as unknown as SignalingMessage);\n return;\n }\n const target = findOpenTarget(msg.targetPeerId);\n if (!target) {\n sendUnknownTarget(ws, msg.targetPeerId);\n return;\n }\n const relayed: SignalingMessage = {\n type: \"signal\",\n peerId: senderId,\n targetPeerId: msg.targetPeerId,\n payload: msg.payload,\n };\n try {\n target.send(relayed);\n } catch {\n peerSockets.delete(msg.targetPeerId);\n sendUnknownTarget(ws, msg.targetPeerId);\n }\n };\n\n return new Elysia().ws(path, {\n message(ws, raw) {\n const msg = parseMessage(raw);\n if (!msg) {\n ws.send({ type: \"error\", reason: \"malformed\" } as unknown as SignalingMessage);\n return;\n }\n if (msg.type === \"join\") {\n handleJoin(ws, msg.peerId);\n return;\n }\n if (msg.type === \"signal\") {\n handleSignal(ws, msg);\n return;\n }\n ws.send({ type: \"error\", reason: \"malformed\" } as unknown as SignalingMessage);\n },\n\n close(ws) {\n const peerId = (ws.data as unknown as Record<string, unknown>).peerId as unknown as\n | string\n | undefined;\n if (peerId) {\n // Only delete the entry if it still points at *this* socket, so a\n // stale close after a reconnect does not evict the new socket.\n if (peerSockets.get(peerId) === (ws as unknown as { send: (m: unknown) => void })) {\n peerSockets.delete(peerId);\n }\n }\n },\n });\n}\n"
|
|
11
11
|
],
|
|
12
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA;;;ACMA;AACA;AACA;AACA;AAiDA,eAAsB,oBAAoB,CACxC,SACyB;AAAA,EAKzB,MAAM,MAAM,OAAO,QAAQ,kBACvB,QAAQ,QAAQ,QAAQ,eAAe,IACvC,IAAI,QAAyB,CAAC,SAAS,WAAW;AAAA,IAChD,MAAM,UAA2B,IAAO,mBACtC;AAAA,MACE,MAAM,QAAQ;AAAA,SACV,QAAQ,SAAS,aAAa,EAAE,MAAM,QAAQ,KAAK;AAAA,IACzD,GACA,MAAM,QAAQ,OAAO,CACvB;AAAA,IACA,QAAQ,KAAK,SAAS,MAAM;AAAA,GAC7B;AAAA,EAOL,MAAM,UAAU,IAAI,uBAClB,GACF;AAAA,EACA,MAAM,UAAU,IAAI,qBAAqB,QAAQ,WAAW;AAAA,EAE5D,MAAM,OAAO,IAAI,KAAK;AAAA,IACpB,SAAS,CAAC,OAAO;AAAA,IACjB;AAAA,EACF,CAAC;AAAA,EAQD,MAAM,KAAK,UAAU;AAAA,EAErB,OAAO;AAAA,IACL;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,OAAO,YAAY;AAAA,MAOjB,WAAW,UAAU,IAAI,SAAS;AAAA,QAChC,IAAI;AAAA,UACF,OAAO,UAAU;AAAA,UACjB,MAAM;AAAA,MAGV;AAAA,
|
|
13
|
-
"debugId": "
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "93C98D54564BCF4764756E2164756E21",
|
|
14
14
|
"names": []
|
|
15
15
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -14,42 +14,10 @@ export { checkPostconditions, checkPreconditions, clearConstraints, isRuntimeCon
|
|
|
14
14
|
export type { ContextConfig } from "./shared/lib/context-helpers";
|
|
15
15
|
export { createContext, runInContext } from "./shared/lib/context-helpers";
|
|
16
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";
|
|
23
17
|
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";
|
|
32
18
|
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";
|
|
45
19
|
export type { Resource, ResourceOptions, ResourceStatus } from "./shared/lib/resource";
|
|
46
20
|
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";
|
|
53
21
|
export { $persistedState, $sharedState, $state, $syncedState } from "./shared/lib/state";
|
|
54
22
|
export type { TestCase, TestSuite } from "./shared/lib/test-helpers";
|
|
55
23
|
export { createTestSuite, quickTest, TestRunner } from "./shared/lib/test-helpers";
|