vercel-labs / vercel-labs/native

ts-core: no official way to route Cmd.request into native code — patch-only on desktop, impossible on mobile

Open
#430 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Zig
Stars
7.7k
Forks
314
Avg merge
5h
Merged PRs (30d)
13

Description

A TypeScript core reaches the host with Cmd.request(name, ...), which routes through runtime.effects.HostCallBinding. HostCallBinding is a public type and bindHostCalls() is public runtime API, but no generated path ever installs an app-authored binding — every generated entry point either wires the service carriers or null, and null rejects all non-reserved host requests (src/runtime/effects.zig: if (!fake and self.host_calls == null ...) return self.rejectStartedHost(...)).

So for the SDK's default authoring path, apps whose effects go beyond the built-in command set — custom IPC to a native daemon/service, BLE/hardware, proprietary vendor SDKs — cannot route commands into native code at all. The src/services escape hatch doesn't apply: services are TypeScript and can never reach C FFI.

What each target does today

  • Desktop: the generated runner hardcodes the choice (src/app_runner/ts_core_main.zig):

    .host_calls = if (comptime use_pool)
        pool_transport.binding()
    else if (comptime use_child)
        child_transport.binding()
    else
        null,   // no app-authored binding possible
    

    Real apps doing native effects work around this by patching the generated runner in zig-cache after the build stages it (add an @import of a host module, wire its binding() in). Undocumented and fragile, but possible.

  • Mobile: the staged mobile wiring (src/app_runner/ts_core_mobile.zig) sets host_calls from the in-process service pool only. There is no generated file an app can plausibly patch — it stages fresh from the SDK template on every build — so this is impossible, not just undocumented.

  • The C ABI shows the intended pattern but stops short: native_sdk_app_set_audio_service / _set_credential_service / _set_image_service are native-side registration seams, but there is no host-call equivalent. bindHostCalls() is only reachable by an embedder that owns the runtime loop — and the iOS tier deliberately owns it (src/tooling/ios.zig: "the toolkit owns the entire iOS app"), locking embedders out of the one API that would solve this.

  • A Zig core avoids the problem by construction (its logic is native code and calls FFI directly), which makes the gap specific to TS cores — the default path.

Notes / proposal

A small symmetric seam in both generated runners. We run this today (against 0.10.1) on mobile and are happy to turn it into a PR:

  • AppOptions.mobile_host (or a manifest declaration — may be cleaner): a module exposing binding() with the same contract as the desktop runner's host_calls value, plus its include dirs / object files.
  • The build graph imports it into the staged mobile wiring as app_host; the wiring references it under a comptime flag so apps without it never touch the import. Implementation note from doing this: the generated options module must be created once and shared between the exports and app modules — a file may belong to only one zig module.
  • Desktop mirrors it in the generated runner's else null fallback.

Happy to reshape to whatever fits (manifest-declared host module seems most in keeping with app.json/app.zon being the single source of app truth).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading src/app_runner/ts_core_main.zig and src/app_runner/ts_core_mobile.zig, then trace HostCallBinding and null handling in src/runtime/effects.zig. Compare the existing pool and child bindings with the staged mobile wiring. Done means an app-authored binding can be selected on desktop and mobile without patching generated files, while apps without one retain current behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript, zig
Domain
api, build-system, desktop, mobile
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.