@automerge/subduction

中文文档
0.27.0发布于 2026/11/04MIT OR Apache-2.0TypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 3515

Wasm/JavaScript bindings for Subduction (browser and Node.js)

原始使用文档
文档目录12 节
  1. Plain-object API
  2. Migration from wrapper classes
  3. Build package
  4. Run tests
  5. Naming Conventions
  6. Debugging with Chrome DevTools
  7. One-time Chrome setup
  8. Building the debug variant
  9. Consuming the debug bundle
  10. Source path resolution
  11. Tradeoffs baked into the profile
  12. Verifying DWARF is present

Subduction Wasm bindings

[!WARNING] This is an early release preview. It has a very unstable API. No guarantees are given. DO NOT use for production use cases at this time. USE AT YOUR OWN RISK.

Plain-object API

Subduction is the only public library class. IDs, digests, and topics are 64-character hex strings; checkpoint prefixes are 24-character hex strings. Input accepts either case, without 0x; output is always lowercase. Blobs and signed envelopes remain Uint8Array. Metadata, sync results, and stream events are recursively plain objects and need no .free() calls.

import {
  Subduction, createMemorySigner, createMemoryStorage,
} from "@automerge/subduction";

const node = new Subduction({
  signer: createMemorySigner(),
  storage: createMemoryStorage(),
});
try {
  await node.storeBatch(treeId, {
    commits: [{ head: commitHash, parents: parentHashes, blob: changeBytes }],
  });
  const results = await node.syncWithAllPeers(treeId, { subscribe: true });
  for (const [peerId, result] of Object.entries(results)) {
    console.log(peerId, result.success, result.stats);
  }
} finally {
  try { await node.close(); } finally { node.free(); }
}

Data arguments are synchronously decoded/copied into owned Rust values before asynchronous work is scheduled. Callers may reuse or mutate their objects and ordinary buffers once a method returns. Inputs are neither consumed nor detached; freezing is optional. Shared-memory payloads are rejected. Outputs own independent JS memory and remain usable after shutdown. Services (storage, signers, policies, transports, callbacks, and cancellation signals) remain live interfaces.

Migration from wrapper classes

This is a breaking API change; there is no implicit legacy-wrapper conversion.

PreviouslyNow
PeerId, CommitId, SedimentreeId, Digest, Topic constructorsHex strings
Checkpoint.fromBytes(bytes)24-character hex prefix (commitId.slice(0, 24) for a valid hex ID)
new MemoryStorage()createMemoryStorage()
IndexedDbStorage.setup(indexedDB, name)createIndexedDbStorage(indexedDB, name)
MemorySigner.generate() / .fromBytes(seed)createMemorySigner() / createMemorySigner(seed)
WebCryptoSigner.setup()createWebCryptoSigner()
new CommitInput(new LooseCommit(...), blob){ head, parents, blob }
new FragmentInput(new Fragment(...), blob){ head, boundary, checkpoints, blob }
storeCommit(id, head, parents, blob)storeCommit(id, { head, parents, blob })
storeBuiltBatch(id, commits, fragments) for ordinary publicationstoreBatch(id, { commits, fragments })
Precomputed metadata publicationstoreBuiltBatch(id, { commits: [{ metadata, blob }], fragments: [...] })
Positional sync boolean/timeout{ subscribe, timeoutMilliseconds } options
PeerResultMap.getResult(peer) / .entries()results[peer] / Object.entries(results)
Depth.valueNumeric depth
Metadata wrappersRecords from getCommits, getFragments, or stored-update streams
AuthenticatedTransport.setup(...) then addConnection(...)node.connectTransport(transport, { expectedPeerId })
Discovery transport setupconnectTransport / acceptTransport with { serviceName }

Custom storage implementations receive hex IDs and signed bytes; loads return { signed: Uint8Array, blob: Uint8Array } records, not classes. The built-in IndexedDB backend retains the existing version-1 stores, indexes, keys, and record layout. Its returned structural object has close(); storage is caller-owned and is not closed by the node. Storage is trusted to preserve locally signed or previously verified envelopes. On reload Subduction checks encoding and blob agreement, but does not re-verify signatures. Storage callbacks are not an admission path for unverified network records.

Custom policies, remote-head observers, ephemeral callbacks, and frame handlers also receive hex identifiers. hashMetricOverride now takes a commit ID string and returns a numeric depth. Existing infallible Rust signer/metric trait contracts still require a valid signature/public key and a nonthrowing valid depth metric; this representation change does not make those traits fallible.

The detailed boundary design is in POJO_API.md. Synchronization, authorization, cancellation, and partial-write guarantees are unchanged.

Build package

From the repository root:

nix build .#subduction-js

This builds and tests the npm package, including the /debug variant, and writes result/subduction.tgz. See Releasing for the tag-driven publishing workflow.

For an in-place development build of subduction_wasm/dist/, use nix develop followed by bodge subduction_wasm instead.

Run tests

Install dependencies:

pnpm install

Install Playwright's browser binaries:

pnpm run test:install

Run tests:

pnpm test

Rebuild and test the public package and its TypeScript declarations:

pnpm run test:pojo

This requires a Wasm-enabled Rust toolchain, wasm-bodge, esbuild, TypeScript, and a wasm-bindgen CLI matching Cargo.lock. Override the Rust toolchain and bindgen location using CARGO_TOOLCHAIN and WASM_BINDGEN if needed. The script rebuilds dist/, typechecks a consumer against the emitted package declarations, and runs Node tests against both release and debug wrappers. Browser tests use that same built public wrapper and require a current subduction_cli (or SUBDUCTION_CLI).

View Playwright report:

pnpm run test:report

Naming Conventions

wasm-bindgen-generated types ownership rules can be confusing and cumbersome, with different behavior depending on which side of the Wasm boundary your type was created from. To help keep this straight, we adopt the naming convention:

[!NOTE] Internal Rust wrapper types are prefixed with Wasm. Types imported from JS are prefixed with Js.

These are implementation conventions, not public JavaScript construction requirements. Private drivers may still use Wasm wrappers internally.

Debugging with Chrome DevTools

Subduction's Wasm packages can ship a parallel ./debug subpath export that preserves DWARF debug info, enabling source-level step-through in Chrome DevTools. This section applies to @automerge/subduction but is also valid for @automerge/sedimentree and @automerge/automerge-subduction.

One-time Chrome setup

  1. Install the C/C++ DevTools Support (DWARF) extension from the Chrome Web Store. It handles reading DWARF sections from Wasm modules; wasm-bindgen output uses the same DWARF format the extension was built for.
  2. In Chrome, visit chrome://flags/#enable-webassembly-debugging and enable the WebAssembly Debugging: Enable DWARF support flag. Restart the browser.
  3. Open DevTools on the page that loads your Wasm bundle, go to the gear icon → Experiments, and confirm WebAssembly Debugging: Enable DWARF support is checked there too.

Building the debug variant

From the workspace root:

bodge subduction_wasm          # one crate
bodge:all                      # all four _wasm crates

Both bodge and bodge:all pass --debug-profile wasm-debug to wasm-bodge, which runs a second cargo build under the [profile.wasm-debug] profile declared in the workspace Cargo.toml. The generated dist/ tree contains both the release artifacts (under the usual paths) and debug artifacts (prefixed with debug- under esm/ / cjs/, plus *-debug.wasm standalone, plus iife/debug.js).

Consuming the debug bundle

Your downstream app imports from the ./debug subpath:

import { init, /* ... */ } from "@automerge/subduction/debug";

For Chrome DevTools to locate DWARF symbols, it must fetch the Wasm binary at a URL it can read. This rules out the inline-base64 path. Use one of:

  • Bundler path (recommended): import @automerge/subduction/debug under a bundler (Vite/Webpack/Rollup). The browser condition resolves to ./dist/esm/debug-bundler.js, which delegates Wasm loading to the bundler's native .wasm import — leaving the binary as a fetchable asset that DevTools can read DWARF from.
  • Manual fetch: serve ./dist/subduction-debug.wasm directly and call initSync(bytes) from @automerge/subduction/slim (bypassing auto-init).

Source path resolution

DWARF embeds absolute paths from the compile host (e.g. /home/expede/Documents/Code/subduction/subduction_core/src/…). For step-through to work, DevTools needs to map those paths to files on disk. Two ways:

  • Preferred: open DevTools on a page served from inside the Subduction checkout so the absolute DWARF paths resolve as-is.
  • Or: in the C/C++ DevTools Support extension's options page, add a path substitution mapping the DWARF paths to your local checkout.

If DevTools shows "<file>: file not found" in the Sources panel, you need to set up the mapping.

Tradeoffs baked into the profile

[profile.wasm-debug] (see the workspace Cargo.toml for the full annotated definition) inherits from release and tunes these knobs:

  • opt-level = 1 — most locals remain visible, line-stepping feels natural; optimized enough for interactive debugging.
  • lto = "thin" — avoids the heavy cross-crate inlining of lto = "fat" that would collapse stack frames.
  • debug = "limited" — keeps line tables, type info, and most locals, at ~½ the DWARF size of debug = "full". Empirically subduction_wasm lands at ~29 MiB raw / ~6 MiB gzipped (versus ~3 MiB / 723 KiB for release).
  • debug-assertions = true and overflow-checks = true — catch bugs the release build wouldn't trip on. Disable these on a branch if you're reproducing a release-only codepath.
  • strip = "none" on the four _wasm crates specifically, overriding the release profile's strip = "debuginfo" (which would otherwise wipe the DWARF).
  • Automerge and sedimentree_core are pinned to opt-level = 2 to keep their hashing/iteration-heavy internals from crawling in the browser. If you need to step through those crates' internals, comment out the corresponding [profile.wasm-debug.package.*] overrides in the workspace Cargo.toml.

Verifying DWARF is present

wasm-tools objdump ./subduction_wasm/dist/subduction-debug.wasm | grep debug_

Should list .debug_info, .debug_line, .debug_str, .debug_abbrev, and friends. If those are missing, something stripped DWARF — most likely an accidental wasm-opt run, or a strip = "debuginfo" leaking through from the release profile overrides.

版本与发布记录

@automerge/subduction 共发布 26 个版本,首次发布于 2026/02/27;过去 12 个月发布了 26 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.27.0latest2026/11/043 天前
0.26.02026/10/2216 天前
0.24.02026/10/081 个月前
0.23.02026/09/301 个月前
0.21.22026/09/131 个月前
0.20.12026/07/313 个月前

关于 @automerge/subduction 的常见问题

@automerge/subduction 是什么?

Wasm/JavaScript bindings for Subduction (browser and Node.js)

如何安装 @automerge/subduction?

在项目目录运行 npm install @automerge/subduction,或使用 pnpm add / yarn add / bun add。

@automerge/subduction 支持 TypeScript 吗?

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

@automerge/subduction 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@automerge/subduction 可以商用吗?

@automerge/subduction 使用 MIT OR Apache-2.0 许可。可在 MIT、Apache-2.0 中任选其一遵守。选择 MIT 时,宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@automerge/subduction 的下载量有多少?

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

@automerge/subduction 还在维护吗?

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