Skip to content

feat(core): demonstrate aube C ABI (bun:ffi) installer behind Npm.Service#37391

Draft
jdx wants to merge 4 commits into
anomalyco:devfrom
jdx:feat/aube-ffi-installer
Draft

feat(core): demonstrate aube C ABI (bun:ffi) installer behind Npm.Service#37391
jdx wants to merge 4 commits into
anomalyco:devfrom
jdx:feat/aube-ffi-installer

Conversation

@jdx

@jdx jdx commented Jul 16, 2026

Copy link
Copy Markdown

Issue for this PR

References #37301 (demonstration companion to the Node-API variant #37300; not intended to land alongside it). Supersedes #37305 — GitHub can't reopen a PR after its head branch was rebased, so this is the same branch rebuilt on aube 1.29.0.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Demonstrates the C ABI / bun:ffi flavor of the same Npm.Service swap proposed in #37301: packages/core/src/npm.ts backed by @jdxcode/aube-ffi instead of @npmcli/arborist. This exists so both aube embedding surfaces can be compared side by side against the Node-API PR; the Node-API variant is the one I'd recommend landing.

How it works: aube_wait blocks the thread that calls it, so each operation runs in a Bun Worker (packages/core/src/aube-ffi/worker.ts) that dlopens the library, starts aube_install/aube_add, posts the operation handle back, then blocks in aube_wait and posts the JSON result. The client (aube-ffi/client.ts) maps the Effect scope's AbortSignal to a main-thread aube_cancel on that handle, so the event loop is never blocked and cancellation works. Offline-first policy is identical to the Node-API PR.

New since #37305 — install progress over the polled transport. Bun can't receive FFI callbacks on aube's threads, so progress arrives via aube 1.29.0's bufferEvents + aube_events_next: operations start with buffering enabled and the main thread — which already dlopens for aube_cancel — drains the event queue on a 50 ms interval while the worker blocks in aube_wait (polling from a different thread than the waiter is the documented pattern). Events still buffered when aube_wait returns are discarded with the handle, so the client synthesizes the terminal event from the authoritative result when the stream didn't deliver one — consumers key UI state off it, matching the Node-API transport. Npm.Service exposes the identical optional EventOptions surface as #37300, so the two backends stay directly comparable. AUBE_FFI_LIBRARY overrides the packaged library path for tests/local dev, mirroring aube's own smoke-test convention.

Notable differences vs the Node-API variant, from actually building both:

  • Raw specifiers pass straight throughaube_add takes an array of spec strings, so git/tarball/alias specs need no name/version splitting (the Node-API input is {name, version} pairs).
  • The host owns more glue: the .npmrc omit=dev|optional → dependency-selection mapping that the Node-API addon does internally had to be reimplemented here, and the worker/cancel/poll choreography is ~200 lines that @jdxcode/aube-node simply doesn't need.
  • Events are polled, not pushed: the Node-API addon delivers callbacks natively; here the 50 ms drain means the stream's tail can be lost to the last poll window (hence the terminal-event synthesis). Fine for spinners; a consumer wanting every event should use the Node-API package.
  • Bun-only: bun:ffi doesn't exist under Node, so this variant can't back the node build.
  • No compiled-binary story here: the Node-API package ships a bunPlugin for embedding one platform addon per compiled target; doing the same for the FFI dylib would need hand-rolled embed/extract wiring, which I deliberately left out of a demo.

How did you verify your code works?

  • Same packages/core npm suite as before (9/9 across npm.test.ts + npm-config.test.ts, including .npmrc omit=dev and file: spec cases); tsc clean; oxlint 0 errors; prettier clean
  • Live e2e against the real registry with the published @jdxcode/aube-ffi@1.29.0: add("express@4.21.2") delivers 272 events (resolving → fetching → linking → complete, 266 progress ticks) and materializes the package; aborting a webpack add mid-flight rejects with ERR_AUBE_INSTALL_CANCELLED — exercising the new main-thread dlopen including aube_events_next
  • Terminal-event synthesis observed doing real work: on warm-cache runs the natural complete phase falls into the final poll window and the synthesized terminal arrives instead

Screenshots / recordings

Not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Written by an AI coding assistant (Claude Code), directed and reviewed by @jdx.

jdx and others added 3 commits July 16, 2026 12:53
…vice

Companion demo to the @jdxcode/aube-node PR: same Npm.Service swap
implemented over @jdxcode/aube-ffi via bun:ffi. aube_wait blocks its
calling thread, so each operation runs in a Bun Worker that posts the
handle back for main-thread aube_cancel. Raw package specifiers pass
straight to aube_add.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bun cannot receive FFI callbacks on aube's threads, so progress arrives
via the C ABI's polled transport (aube 1.29.0's bufferEvents +
aube_events_next): the worker keeps blocking in aube_wait while the
main thread — which already dlopens for aube_cancel — drains the event
queue on a 50ms interval. Events still buffered when aube_wait returns
are discarded with the handle, so the client synthesizes the terminal
event from the authoritative result when the stream didn't deliver one;
consumers key UI state off it, matching the Node-API transport.

Npm.Service gains the same optional EventOptions surface as the
aube-node branch so the two backends stay directly comparable.
AUBE_FFI_LIBRARY overrides the packaged library path, mirroring aube's
own smoke-test convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The polled event transport dlopens aube_events_next, which first
shipped in the 1.29.0 C ABI libraries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Version parity with the Node-API branch; 1.29.1 carries the
minimumReleaseAge add-pin fix (jdx/aube#1056).

Co-Authored-By: Claude Fable 5 <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