@fairfox/polly

中文文档
0.100.0发布于 2026/11/04MITTypeScriptES Module零依赖近 30 天下载 3060

具有Chrome扩展、PWAs和工人应用反应状态和跨文本信息的多执行文本框架

中文使用文档
文档目录19 节
  1. 投篮
  2. 各地同步状态
  3. 同级第一状态(v0.210
  4. 节点和邦是一等的网状同龄人
  5. 大文件的 Blob 存储
  6. 插入的验证
  7. 第1步:向管理人员增加合同
  8. 步骤2: 定义状态界限
  9. 第三步:运行它
  10. 第4步: 抓住模特儿从未见过的道路
  11. 没有Docker的安全通道更快
  12. 链接到模型的可执行规格
  13. 可接触性证人
  14. 快开始
  15. 实例
  16. 国 际
  17. 质量工具
  18. 用克劳德代码
  19. 许可证

@ fairfox/ polly (英语)

多文本应用的活性状态——从单进程信号到对等加密网格——有正式的验证.

投篮

定义状态一次。 随处读取. 波利让他们同步.

// src/shared/state.ts
import { $sharedState } from "@fairfox/polly/state";

export const counter = $sharedState("counter", 0);
// src/background/index.ts — service worker, extension background, Node process
import { createBackground } from "@fairfox/polly/background";
import { counter } from "../shared/state";

const bus = createBackground();

bus.on("INCREMENT", () => {
  counter.value++;
  return { count: counter.value };
});
// src/popup/index.tsx — browser popup, any Preact/React UI
import { render } from "preact";
import { counter } from "../shared/state";

function App() {
  return (
    <div>
      <p>Count: {counter.value}</p>
      <button onClick={() => counter.value++}>+</button>
    </div>
  );
}

render(<App />, document.getElementById("root")!);

背景写道: counter弹出会读出 两者保持同步——没有消息传递,没有订阅管理. 国家是 预告信号,因此当值变化时,UI会自动重报.

各地同步状态

现代应用在多个孤立的情况下运行代码:服务工作者,内容脚本,服务器进程. 保持它们之间的状态一致意味着为每一个数据写同步逻辑. 波利用四个原始人来代替它:

初级同步持久性用于
$sharedState对对用户数据、设置、 认证—— 任何在重新启动后能够存活并保持一致的数据
$syncedState对没有Ephemeral 共享状态:连接状态,直播协作旗
$persistedState没有对内文设置, 窗体草稿
$state没有没有本地用户界面状态: 装入自旋器、 模式能见度

所有四个回回预告信号. 读取 .value,用 .value =。 。 。 .

import { $sharedState, $syncedState, $persistedState, $state } from "@fairfox/polly/state";

const user = $sharedState("user", { name: "Guest", loggedIn: false });
const wsConnected = $syncedState("ws", false);
const draft = $persistedState("draft", "");
const loading = $state(false);

对于同步数据, $resource 当依赖关系发生变化时自动获取并重排:

import { $resource } from "@fairfox/polly/resource";

const todos = $resource("todos", {
  source: () => ({ userId: user.value.id }),
  fetcher: async ({ userId }) => {
    const res = await fetch(`/api/todos?userId=${userId}`);
    return res.json();
  },
  initialValue: [],
});

todos.data;   // Signal<Todo[]>
todos.status; // Signal<"idle" | "loading" | "success" | "error">
todos.refetch();

同级第一状态(v0.210

上面的4个原始人保持状态一致 在单一的部署。 但有些应用程序需要能够活过服务器离开的状态,或者说服务器根本没有看到. Polly现在提供三层抗御能力, 每一个不同的原始家庭:

级别初级服务器的作用复原力
弱点$sharedState真相来源服务器备份
中间$peerState在数据路径上完全对等任何设备都可以给服务器补水
最强$meshState未在数据路径上App 与零服务器同步工作

$peerState 每个设备都装满了 自动合并 CRDT复制版. 服务器也持有一个,因此cron和HTTP处理器可以读取并变换文档. 如果服务器丢失了存储,任何重新连接的客户端都会通过正常的同步协议重新聚集.

import { createPeerStateClient, configurePeerState, $peerState } from "@fairfox/polly/peer";

const client = createPeerStateClient({ url: "wss://yourapp.com/polly/peer" });
configurePeerState(client.repo);

const settings = $peerState("settings", { theme: "dark" });
await settings.loaded;
settings.value = { theme: "light" }; // syncs to every peer

$meshState ——同行直接通过WebRTC数据通道进行交换操作,与Ed25519签约,并用XSalsa20-Poly1305加密. 没有服务器看到数据 。 一个小型的无国籍信号服务器可以帮助同伴找到对方;去除它不会影响运行中的连接.

import { configureMeshState, $meshState, MeshNetworkAdapter, MeshWebRTCAdapter } from "@fairfox/polly/mesh";

const repo = new Repo({ network: [new MeshNetworkAdapter({ base: webrtcAdapter, keyring })] });
configureMeshState(repo);

const notes = $meshState("notes", { entries: [] });
// Operations flow peer-to-peer, signed and encrypted

设备之间的第一次密钥交换使用以QR代码显示的对接符. 通过向每个同行传播的签名吊销记录,撤销妥协装置.

三层并存在一个应用程序中——公共环境 $sharedState中协作文档 $peerState,私人笔记 $meshState。见 文件/统计数据库 整个决策树和 文件/RFC-041-Choosing.md 设计原理.

节点和邦是一等的网状同龄人

Archive cron, LLM 代理, admin CLIs, 无头桥——每名总参与者都会得到与浏览器相同的状态原始, 而不使用猴子- patching globals 或写作活塞运输接线. Polly 将一个接受注射式运输和储存的工厂:

import { createMeshClient, $meshState } from "@fairfox/polly/mesh";
import { bootstrapCliKeyring, fileKeyringStorage } from "@fairfox/polly/mesh/node";
import { RTCPeerConnection } from "werift";              // or '@roamhq/wrtc'

const storage = fileKeyringStorage("~/.fairfox/keyring.json");
const keyring = await bootstrapCliKeyring({ storage });   // first run prompts for a pairing token

const client = await createMeshClient({
  signaling: { url: "wss://example.com/polly/signaling", peerId: "cli-a1b2" },
  rtc:       { RTCPeerConnection },
  keyring,
});

const doc = $meshState("agenda", { items: [] });
await doc.loaded;
await client.close();

createMeshClient 是运行时间不可知的——在浏览器中 rtc 选项是可选的,因为 globalThis.RTCPeerConnection 已存在。 这个 @fairfox/polly/mesh/node 子路径添加文件系统支持的密钥环存储, 原子写到 0600 权限, 并有固定的靴子。 也没有 werift 也没有 @roamhq/wrtc 捆绑; 两者被宣布为可选的对等依赖。 选一个适合你部署的 werift 安装干净的任何地方(纯类型脚本,没有本地的脚本), @roamhq/wrtc 更快, 但需要为您的平台预建二进制 .

大文件的 Blob 存储

CRDT文件不应携带二进制有效载荷——操作历史随着每次同步而增长. Poly 运送一个内容地址为blob的商店,通过与WebRTC相同的WebRTC频道传输文件对等 $meshState,没有服务器存储。 文档具有轻量级 BlobRef 值; 字节活在本地索引 DB 缓存中, 在64 Kib 块的同位素之间移动.

import { createBlobStore, createBlobRef } from "@fairfox/polly/mesh";

const blobs = createBlobStore(webrtcAdapter, { encrypt: { key: docKey } });

// Sender
const bytes = new Uint8Array(await file.arrayBuffer());
const ref = await createBlobRef({ bytes, filename: file.name, mimeType: file.type });
await blobs.put(ref, bytes);       // caches locally, announces to peers
doc.value = { ...doc.value, attachment: ref };

// Receiver (on any connected peer)
const received = await blobs.get(ref.hash);  // fetches from peers, verifies hash
const url = await blobs.url(ref.hash);        // object URL for <img src>

SHA-256内容涉及同行和文件之间的重复。 加密是可选的——当配置时,发送者会加密一次(XSalsa20-Poly1305),并将密码文本块块;接收者会重新组装,解密,并验证平正文字散列相对于 BlobRef。见 文件/RFC-042-blob-sync.md 为设计.

插入的验证

一个弹出和一个背景脚本都写给同一个州. 内容剧本读取中后期. 测试错过这些bug,因为它们取决于时机.

Polly 生成 TLA+ 东加 ,然后用 TLC 检查。 不学一新语. 你记下你已经写的.

第1步:向管理人员增加合同

requires() 财务报告和审定财务报表 ensures() 是运行时间 无操作. polly verify 静地读取.

import { createBackground } from "@fairfox/polly/background";
import { requires, ensures } from "@fairfox/polly/verify";
import { user, todos } from "./state";

const bus = createBackground();

bus.on("TODO_ADD", (payload: { text: string }) => {
  requires(user.value.loggedIn === true, "Must be logged in");
  requires(payload.text !== "", "Text must not be empty");

  todos.value = [...todos.value, {
    id: Date.now().toString(),
    text: payload.text,
    completed: false,
  }];

  ensures(todos.value.length > 0, "Todos must not be empty after add");

  return { success: true };
});

步骤2: 定义状态界限

一个验证配置告诉 TLC 需要探索的值 :

// specs/verification.config.ts
import { defineVerification } from "@fairfox/polly/verify";

export const verificationConfig = defineVerification({
  state: {
    "user.loggedIn": { type: "boolean" },
    "user.role": { type: "enum", values: ["guest", "user", "admin"] },
    todos: { maxLength: 1 },
  },
  messages: {
    maxInFlight: 2,
    maxTabs: 1,
  },
});

第三步:运行它

$ polly verify

Generating TLA+ specification...
Running TLC model checker...

  Model checking complete.
  States explored: 1,247
  Distinct states: 312
  No errors found.

All properties verified.

A 类 requires() 不坚持勘探: 处理者不是 被带走了,所以模特儿永远无法到达守卫后面的状态. 这就是为什么 requires() 便宜,这也是为什么一个绿色跑步 不说是否 警卫可能被侵犯——安全财产是已经停止的系统.

添加 liveness: true (每个配置,或每个子系统)检查另一半: 任何寄出的东西都会永远等待 TLC 然后报告准确的顺序 信息永远无法解决。 因为它是默认关闭的 价格昂贵——在11个子系统模型上测出,一个验证门从~110到 ~512 (英语).

对于更大的应用程序, 子系统范围核查 将状态空间拆分, 以快速检查 .

第4步: 抓住模特儿从未见过的道路

每次跑步都报告 模式覆盖 ——宣布每个处理者写字的字段,而模型上的任何字段都不触及. 它捕捉到的bug是信息总线上赋予的能力: a register() 设定方法 canSend 而不是直接通过一个派出的 守卫的操作员。 TLC对此没有过渡性,突变测试没有模拟线来进行突变,而绿色跑道则一言不发.

没有处理器写入的字段被标记为无法到达状态 。 未分配的函数或方法写作的字段——即使是真正的处理者也写作的字段——被标记为在任何模式过渡外被变异. 添加 --strict 无法关闭,所以没有模型的写作不能滑出绿色.

$ polly verify --strict

⚠️  1 declared state field write(s) outside any modelled transition:
   • session.canSend mutated in RecoveryFlow.register() — src/background/index.ts:42

❌ Strict mode: model coverage incomplete

没有Docker的安全通道更快

polly verify --engine bun 使用花粉公司自己的模型检查器检查同一生成的光谱,正在进行中:变化和僵局,没有多克克,没有JVM. 举例来说,它报告TLC精确的状态计数为2.5–9×所有核心的TLC速度,为记忆的五分之一到一半. 它不检查时间属性( ensures 附加条件:它列出这些条件没有检查 --strict 失败了 用于您发车前的编辑和检查回路和 TLC(默认) 。 Bun 检查器无法读取的光谱返回到 TLC 。 设定 verification: { engine: "bun" } 让它成为工程默认.

polly bdd 加上第三个核查层 verify (TLA+)和 (中文(简体) ) mutate (模拟测试):从用户角度撰写的接受实例,如Gherkin,横穿 真实工厂边界 一样 createBackground 你的扩展靴子,从来没有一个手电线公交车 静悄悄地记录了真正的路径的缺口.

# features/auth.feature
Feature: Signing in
  Scenario: A signed-out user can sign in
    Given the user is signed out
    When the user signs in as "admin"
    Then the user is signed in
    And their role is "admin"

一个 defineStep 约束是双重用途, 同样的把戏使 requires()编号 :ensures() (单位:千美元) given编号 :when编号 :then 驱动运行时间, 而其运行时间 message编号 :stateExpr 静态读取。 因此,示例层,形式层和突变层都描述了相同的处理器和状态.

polly bdd          Run the scenarios across the real boundary (red before green)
polly bdd check    Cross-check every scenario against the verification config (Docker-free)
polly bdd new      Scaffold a feature stub seeded from the verify vocabulary

丢弃 .feature 文件输入您的工程和 polly test 发现一个 bdd 自动分级.

可接触性证人

polly verify --witness 关闭示例层和模型之间的循环。 它将每个场景的结果变成每场场景的TLC可达性检查:一个场景,详尽的模型证明 无法到达 是一个在跑道上是绿色的,在模型上是不可能的,它失败了跑道。 诚实的人可以回来,反例追踪是用户为了到达那里而会走的道路. 无法观察的结果(只检查反应)被报告为跳过.

标记一个情景 @forbidden 维护 相对:一个绝不能发生的州. 然后,当模型证明这种状态无法达到时,证人才会通过,如果能够达到这种状态,就会失败——有缺陷的痕迹。 从同名Gherkin那里看,可以证明是可能的、不可取的结果是不可能的.

$ polly verify --witness

Reachability witnesses (--witness)
  ✓ reachable   Signing in › A signed-out user can sign in
  ✓ reachable   Signing in › Signing out returns the user to a guest session
  · skipped     Todos › Completing a todo that does not exist is refused

Witness result: ✓ PASS

快开始

bun add @fairfox/polly preact @preact/signals

作者工具加载一个生产安装的包并不需要,所以它们是可选的对等依赖. 添加您作为 dev 依赖运行的命令 :

命令/进入点差异依赖性
polly verifytypescript-7@npm:[email protected]
polly verify --witnesstypescript-7@npm:[email protected] @cucumber/[email protected] @cucumber/[email protected]
polly visualizetypescript-7@npm:[email protected]
polly mutate, (中文(简体) ), @fairfox/polly/mutate[email protected] [email protected] (单位:千美元) typescript-7 用于类型过滤器)
polly bdd, (中文(简体) ), @fairfox/polly/bdd@cucumber/[email protected] @cucumber/[email protected]
# all of them
bun add -d typescript-7@npm:[email protected] [email protected] [email protected] \
  @cucumber/[email protected] @cucumber/[email protected]

typescript-7 必须保留该别名:花粉进口 typescript-7/async。其软件包缺失并打印安装命令.

脚手架项目:

polly init my-app --type=extension  # or: pwa, websocket, generic

或者从其中一个例子开始:

git clone https://github.com/AlexJeffcott/polly.git
cd polly/examples/minimal
bun install && bun run dev

实例

示例所显示的
最小计数器 $sharedState 最简单的Polly应用
待办事宜列表带 CRUD 的 requires()编号 :ensures(),子系统范围核查
完全定制具有所有框架特性的生产Chrome扩展
elysia-to-app (英语)配有 Elysia + Bun 的全网应用, 离线第一
网络rtc- p2p- 聊天通过 WebRTC 数据频道对等聊天
团队任务管理者基于作用的合作任务 $constraints() 和经核实的紧急任务清点

国 际

polly init [name] [--type=TYPE]   Scaffold a new project (extension | pwa | websocket | generic)
polly build [--prod]              Build for development or production
polly dev                         Build with watch mode
polly check                       Run all checks (typecheck, lint, test, build)
polly typecheck                   Type-check your code
polly lint [--fix]                Lint (and optionally auto-fix)
polly format                      Format your code
polly test [args]                 Run unit tests (bun test)
polly test --tier=T | --all | --list
                                  Run the multi-tier suite (unit → integration → browser
                                  → e2e → verify) with timed reports
polly test:browser [dir]          Run *.browser.{ts,tsx} in Puppeteer
polly coverage                    Per-file coverage policy and orphaned-test detection
polly verify                      Run formal verification (TLA+/TLC)
  --setup | --validate | --estimate   Generate, check, or size the verification config
  --strict                        Fail on model-coverage gaps
  --engine bun                    Safety pass on polly's own checker, no Docker
  --witness                       Per-scenario reachability checks for your BDD outcomes
polly bdd [run|check|new]         Executable Gherkin across the real boundary, linked to the model
polly mutate [init|run|report|verify|decisions|migrate]
                                  Mutation testing and useless/redundant-test detection
polly visualize                   Generate architecture diagrams (Structurizr DSL)
  --snapshot <path>               Overlay a captured mesh peer-state snapshot
  --model <file.ts>               Write a declared C4 model instead of analysing source
  --export | --serve              Static diagram site (export needs Docker) and a local server
polly gallery [--build|--open]    Serve or export the polly-ui component gallery
polly quality [args]              Run conformance checks (no-as-casting, boundaries, secrets,
                                  server-imports, forbidden-deps, banners, marketing, ...)

每个命令都要 --help。 。 。 . polly mutate 记录在 工具/元件/README.md。 。 。 .

质量工具

Poly ship 兼容性检查和浏览器测试工具,消耗应用程序可以直接采用.

无所现相. 禁止类型脚本 as 类型断言全码基(仅限 as const 和明确的逃生舱门 as unknown as (允许)。 违规行为包括特定模式的固定建议。 运行为 CLI :

polly quality [--root <dir>] [--exclude-packages <names>] [--exclude-files <names>]

或按程序导入, 以便融入自定义检查脚本 :

import { checkNoAsCasting } from "@fairfox/polly/quality";

const result = await checkNoAsCasting({ rootDir: process.cwd() });
if (result.violations.length > 0) {
  result.print();
  process.exit(1);
}

浏览器测试用具. 一个基于 Puppeteer 的用于测试仅浏览器代码的套接字(WebRTC适配器,Preact组件,任何需要真实的 DOM ). 用 Bun.build 装入每个测试文件,在麻黄口上服务,并收集结果:

import { describe, test, expect, done, flush, cleanup } from "@fairfox/polly/test/browser";

describe("my component", () => {
  test("renders", async () => {
    render(<MyComponent />, app);
    await flush();
    expect(app.querySelector("h1")).toHaveTextContent("Hello");
    cleanup(app);
  });
});

done();

运行 polly test:browser tests/browser。 。 。 .

用克劳德代码

这艘反转船有一个 Claude 代码技能 覆盖了完整的 Polly API —— 状态原始,同位素第一和网状状态,验证,质量工具,以及浏览器测试用具. 安装在您的工程中, 这样克劳德可以帮助您将 Polly 与 完整 上下文 整合 :

# From your project directory
claude install-skill /path/to/polly/.claude/skills

那就问问克洛德吧

  • "Polly会怎样融入这个项目?"
  • "给我的处理者增加核查"
  • "用爱丽西亚服务器设置美国"
  • 拨打CI的不挂号检查

如果你保持你自己的克洛德代码技能,考虑在您的项目特有技能中加入波利的状态级决策树和验证模式,这样克洛德就可以为每一个状态推荐合适的原始.

许可证

麻省理工学院


GitHub 图像 • 妇女 问题 • 妇女 实例

版本与发布记录

@fairfox/polly 共发布 157 个版本,首次发布于 2025/10/24;过去 12 个月发布了 149 个版本。

版本发布日期距今
0.100.0latest2026/11/0410 天前
0.99.02026/11/0410 天前
0.98.22026/10/3015 天前
0.98.12026/10/3015 天前
0.98.02026/10/2915 天前
0.97.12026/10/2618 天前

关于 @fairfox/polly 的常见问题

@fairfox/polly 是什么?

具有Chrome扩展、PWAs和工人应用反应状态和跨文本信息的多执行文本框架

如何安装 @fairfox/polly?

在项目目录运行 npm install @fairfox/polly,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:preact(^11.0.1)、@preact/signals(^2.5.1)、@preact/signals-core(^1.14.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 polly,可以用 npx polly 直接运行。

@fairfox/polly 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@fairfox/polly 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@fairfox/polly 可以商用吗?

@fairfox/polly 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@fairfox/polly 的下载量有多少?

根据 npm 官方下载统计,@fairfox/polly 在 2026/10/14 – 2026/11/12 这 30 天内被下载 3,060 次,日均约 102 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@fairfox/polly 还在维护吗?

最新版本 0.100.0 发布于 2026/11/04(10 天前);至今共发布 157 个版本,其中过去 12 个月发布了 149 个;首次发布于 2025/10/24。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。