Skip to content

feat(ffi): Node.js bindings (napi-rs) - #231

Draft
Nic-dorman wants to merge 4 commits into
mainfrom
feat/node-ffi
Draft

feat(ffi): Node.js bindings (napi-rs)#231
Nic-dorman wants to merge 4 commits into
mainfrom
feat/node-ffi

Conversation

@Nic-dorman

Copy link
Copy Markdown
Member

What

Adds ffi/node — a Node.js / Electron binding for the daemon-less Autonomi
client, the sibling of the Python effort. UniFFI has no maintained Node backend,
so this is a hand-written napi-rs wrapper: #[napi] types that
delegate to ant-ffi's plain lib target. No business logic lives in this crate —
it mirrors the same inner client the Swift/Kotlin/C#/Python bindings wrap.

Published as @withautonomi/ant-ffi (scoped, distinct from the daemon SDK's
antd package). Install pulls a prebuilt per-platform .node — no Rust toolchain,
no node-gyp.

Surface

  • 9 connect* constructors (local / default / default+wallet /
    default-external-signer / custom peers ± wallet / devnet-manifest ±
    external-signer / external-signer), all with optional Android dataDir
  • ~30 Client methods: chunks, public/private data, files (+progress),
    cost estimate (+progress), streaming downloads-to-file (+progress), data-map
    helpers, walletApprove, and the full external-signer flow
    (prepare*paymentTransactionsfinalizeUpload / finalizeUploadMerkle
    / cancelUpload)
  • Wallet (fromPrivateKey/address/balanceOf*) + free fns
    antFfiVersion, networkInfo, waitForReceipt, merkleWinnerPoolHash
  • 6 enums, ~15 records, auto-generated index.d.ts (TS autocomplete out of the box)

How it works

  • async fnPromise<T> on napi's tokio runtime; the event loop never blocks
  • _with_progress methods bridge a plain JS (progress) => void callback to
    ant-ffi's ProgressListener via a napi ThreadsafeFunction (safe from the
    core's background progress task)
  • Buffer/Uint8Array zero-copy for payloads; token/gas amounts stay decimal
    strings (they exceed 2^53)

Also re-exports ant-ffi's free functions at its crate root so this crate can call
them (previously only wired into the UniFFI scaffolding).

Testing

  • ✅ Clean build, zero warnings; index.d.ts generates correctly
  • 7/7 offline smoke tests (node:test): import, version, offline EVM-address
    derivation, networkInfo, full-surface presence, free-fn rejection
  • Live devnet round-trip: paid public-data upload → byte-identical download;
    file upload with 10 progress callbacks across Encrypting→Quoting→Storing;
    file download byte-identical. Validates the async + progress-bridge + payment
    chain against ant-core 0.5.1 with zero protocol skew.

Not in this PR (follow-ups)

  • Distribution/CI: the 8-target napi cross-compile GitHub Actions matrix +
    per-platform npm packages + napi prepublish (publish needs an npm org/token).
  • Richer error mapping (machine-readable code per variant; PartialUpload's
    money-visible fields on the JS error).

Draft pending the CI/distribution follow-up and review.

🤖 Generated with Claude Code

Nic and others added 4 commits August 10, 2026 11:09
Add ffi/node, a hand-written napi-rs wrapper exposing the ant-ffi
direct-network client to Node.js and Electron. UniFFI has no maintained
Node backend, so this mirrors the same surface with #[napi] wrappers that
delegate to ant-ffi's plain `lib` target — no business logic in this crate.

- Full surface: 9 connect* constructors, chunk/data/file/estimate/download
  methods, data-map helpers, wallet, and the complete external-signer flow
  (prepare/paymentTransactions/finalize/finalizeMerkle/cancel), plus the
  network_info/wait_for_receipt/merkle_winner_pool_hash free fns.
- Progress: *_with_progress methods bridge a JS callback to ant-ffi's
  ProgressListener via a napi ThreadsafeFunction (background-thread safe).
- async fns return Promises on napi's tokio runtime; Buffer/Uint8Array
  zero-copy; amounts stay decimal strings; auto-generated index.d.ts.
- Packaging: @withautonomi/ant-ffi (scoped, distinct from the daemon SDK's
  `antd`), napi8 (Node 16+), 8-target platform matrix declared.
- Offline smoke tests (node:test) + a live-devnet round-trip demo.

Also re-exports ant-ffi's free functions at its crate root so downstream
Rust consumers (this crate) can call them — previously only wired into the
UniFFI scaffolding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ci-node.yml: offline smoke tests (native build) on Linux/macOS/Windows ×
  Node 18/20/22, plus the full 8-target cross-compile matrix (mac x64/arm64,
  win x64/arm64, linux x64/arm64 × gnu+musl). Runs on PRs touching ffi/node or
  ffi/rust. gnu via --use-napi-cross (portable glibc 2.17 sysroot), musl via
  Zig + cargo-zigbuild.
- publish-node.yml: builds the matrix and publishes @withautonomi/ant-ffi plus
  its per-platform packages to npm in lockstep (napi prepublish + npm publish,
  provenance/OIDC + NPM_TOKEN). Triggers on a node-v* tag or manual dispatch;
  inert until the npm org + token are set up (see the workflow header).

Validated locally: `napi create-npm-dirs` generates all 8 platform packages
with correct name/cpu/os/libc; `napi artifacts --output-dir` enforces the
all-targets-present lockstep before assembly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First CI run surfaced setup issues (not compile failures — mac builds and the
Node-22 native builds passed):
- Windows build "jobs" failed only on a `ls -la` diagnostic step (PowerShell has
  no `-la`); the build itself succeeded. Dropped the step.
- Tests failed on Node 18/20: @napi-rs/cli needs Node >= 20.12 (util.styleText)
  and `node --test "<glob>"` only expands globs on Node 21+. Fix: test matrix is
  Node 20/22, and the test script is bare `node --test` (filename-pattern
  discovery, ignores examples/).
- linux-gnu --use-napi-cross failed: a macOS-generated package-lock omits the
  linux @napi-rs/cross-toolchain optional dep, so `npm ci` skipped it. Use
  `npm install` so npm resolves the runner-platform optional binaries.
- linux-musl: replaced the flaky setup-zig action (404/502 mirror) with a
  deterministic `pip install ziglang` that cargo-zigbuild picks up.

Also corrected the publish workflow's setup header: @WithAutonomi already exists
on npm (holds the prior-network client) — the prerequisite is v2 publish access
or a fresh scope, not creating the org.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
--use-napi-cross keeps hitting the npm optional-dependency bug (npm/cli#4828) on
CI even with `npm install` — the cross-toolchain native binding isn't found. The
musl targets already build fine via cargo-zigbuild (-x), and that path
cross-compiles the full ant-core native tree (incl. secp256k1/blst C deps)
cleanly. So route all four Linux targets through Zig instead. Leaves the six
non-Linux targets and all tests unchanged (already green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant