Skip to content

Support server-side Node/Bun clients: exit status, @blit-sh/core/node, nodejs wasm#65

Merged
pcarrier merged 1 commit into
mainfrom
feat/server-side-node-client
Jun 12, 2026
Merged

Support server-side Node/Bun clients: exit status, @blit-sh/core/node, nodejs wasm#65
pcarrier merged 1 commit into
mainfrom
feat/server-side-node-client

Conversation

@pcarrier

@pcarrier pcarrier commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Context

These changes come from a downstream consumer that runs a @blit-sh/core client server-side (Node/Bun) to drive a local blit server over a unix socket. They had to re-implement several things that are really library internals. This PR moves that work upstream. Observed against @blit-sh/browser@0.34.0 / @blit-sh/core BlitWorkspace.

Note: a few of the original asks were already in-tree and just needed a release + discoverability:

  • Node/Bun/Deno unix transports already exist (c577adb) — but only under @blit-sh/core/transports with a node/browser export condition, not discoverable from the root. This PR adds an explicit @blit-sh/core/node entry + docs.
  • nullLogger and the BlitWorkspace root export already exist. Documented in EMBEDDING.md.

What this PR does

1. Exit status in the JS client (@blit-sh/core)

The client received S2C_EXITED but discarded the exit_status field, so a JS consumer could not get an exit code without re-decoding the frame. Now:

  • S2C_EXITED parses exit_status ([0x08][pty_id:2][exit_status:4], i32 LE; tolerates legacy 3-byte frames → EXIT_STATUS_UNKNOWN).
  • New BlitSession.exitStatus: number | null.
  • New exports exitCodeFromStatus, formatExitStatus, EXIT_STATUS_UNKNOWN — direct ports of format_exit_status/exit_code_from_status in crates/cli/src/agent.rs (unknown → 1, normal → code, signalled → 128 + signal), so the convention lives in one place.

2. @blit-sh/core/node entry

A non-browser subpath that re-exports NodeUnixSocketTransport / BunUnixSocketTransport / DenoUnixSocketTransport plus a new loadBlitWasm() helper. Deliberately not re-exported from the package roottransports/unix.ts imports node:net at module top, which would poison browser bundles (the same reason transports/browser.ts omits them).

loadBlitWasm() initializes @blit-sh/browser off-browser: the published package is a --target web build whose init() assumes a browser fetch(new URL(...wasm, import.meta.url)); under Node/Bun that rejects with an opaque, stackless error. The helper resolves the colocated blit_browser_bg.wasm, reads its bytes and calls init({ module_or_path }), so consumers never touch raw wasm bytes and a bad asset throws a real filesystem error. It also transparently passes through a self-initializing build (see #3).

3. Self-initializing --target nodejs wasm (@blit-sh/browser/node)

New nix derivation browserWasmNode builds the crate with wasm-pack --target nodejs and publishes it under the @blit-sh/browser/node subpath. This build reads its .wasm from disk and instantiates synchronously on import — no fetch, no init() dance. The package root . export stays the --target web build, so existing browser consumers and the ./blit_browser_bg.wasm deep-import are unchanged.

The --target nodejs output has a wasm-bindgen quirk: it emits CommonJS glue but copies the inline canvas snippets verbatim as ES modules, so the generated require() throws. The derivation rewrites those snippets to CommonJS and marks the subtree {"type":"commonjs"}.

Validation

  • @blit-sh/core: tsc --noEmit clean; 332 tests pass (9 new in exit-status.test.ts, plus a pushExited mock helper). Full pnpm -r build + pnpm -r typecheck green across core/react/solid/ui/website.
  • browserWasmNode builds via nix build and the realized artifact self-initializes headless under both require() and import (new Terminal() works).
  • loadBlitWasm() validated end-to-end (Node + Bun) against the real published @blit-sh/browser@0.34.0 web build (resolve → readFile 84KB → init → new Terminal()).
  • The published-package exports map validated in a clean npm install: root . → web build (init is a function), ./node → self-init build (ESM + CJS), ./blit_browser_bg.wasm deep-import preserved, under both default and browser conditions.

Notes for reviewers

  • crates/browser/pkg is gitignored (fetched/built), so it's not in the diff.
  • The exports map keeps . = web build specifically so existing browser consumers (and the consumer's current import init flow) are unaffected; ./node is purely additive.

…s wasm

Addresses gaps that force a server-side consumer (a Node/Bun client driving
a local `blit server` over a unix socket) to re-implement library internals.

@blit-sh/core:
- Parse `exit_status` from S2C_EXITED (was discarded) and expose it as
  `BlitSession.exitStatus`. Export `exitCodeFromStatus`/`formatExitStatus`/
  `EXIT_STATUS_UNKNOWN`, ported from crates/cli/src/agent.rs so JS consumers
  no longer re-derive the `128 + signal` convention.
- Add a non-browser `@blit-sh/core/node` subpath re-exporting the existing
  Node/Bun/Deno unix-socket transports plus `loadBlitWasm()`, a helper that
  initializes @blit-sh/browser off-browser (reads the colocated
  blit_browser_bg.wasm and feeds init({module_or_path})) so consumers never
  touch raw wasm bytes. Kept out of the package root so node:net never leaks
  into browser bundles.

@blit-sh/browser (nix):
- Build a self-initializing wasm-bindgen `--target nodejs` artifact and
  publish it under the `@blit-sh/browser/node` subpath (CommonJS, reads its
  .wasm from disk on import). The root `.` export keeps the `--target web`
  build, so existing browser consumers and the `.wasm` deep-import are
  unchanged.

Docs:
- EMBEDDING.md gains a server-side Node/Bun section covering the unix
  transport, loadBlitWasm, exitStatus and nullLogger.

All @blit-sh/core typechecks + 332 tests pass; the nodejs wasm artifact and
loadBlitWasm are validated end-to-end (Node + Bun) against the published
@blit-sh/browser@0.34.0.
@pcarrier pcarrier marked this pull request as ready for review June 12, 2026 01:34
@github-actions

Copy link
Copy Markdown

🔗 Preview: https://blit-4m40bj8z3-indent.vercel.app

@github-actions

Copy link
Copy Markdown

Coverage

Crate Lines Functions Regions
alacritty-driver 63.7% (626/982) 67.1% (49/73) 64.3% (919/1429)
browser 0.0% (0/807) 0.0% (0/65) 0.0% (0/1370)
cli 28.1% (1208/4299) 41.3% (171/414) 31.1% (2182/7019)
compositor 1.0% (93/9234) 2.0% (8/400) 1.2% (146/12408)
fonts 76.8% (486/633) 85.5% (47/55) 77.9% (922/1183)
gateway 25.7% (362/1411) 29.0% (36/124) 19.4% (449/2318)
proxy 18.3% (150/818) 20.9% (24/115) 20.4% (260/1277)
remote 71.5% (1975/2763) 81.4% (188/231) 74.1% (3737/5045)
sd-notify 73.9% (68/92) 100.0% (6/6) 83.2% (109/131)
server 18.7% (2186/11701) 33.6% (251/748) 20.1% (3660/18180)
ssh 1.9% (7/374) 3.2% (1/31) 0.7% (4/613)
webrtc-forwarder 2.7% (72/2624) 2.1% (4/187) 1.2% (50/4335)
webserver 63.5% (753/1185) 70.8% (121/171) 67.8% (1380/2034)
Total 21.6% (7986/36923) 34.6% (906/2620) 24.1% (13818/57342)

@pcarrier pcarrier merged commit 5e393ef into main Jun 12, 2026
10 of 12 checks passed
@pcarrier pcarrier deleted the feat/server-side-node-client branch June 12, 2026 01:42
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