vercel-labs / vercel-labs/native
ts-core: no official way to route Cmd.request into native code — patch-only on desktop, impossible on mobile
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 possibleReal apps doing native effects work around this by patching the generated runner in zig-cache after the build stages it (add an
@importof a host module, wire itsbinding()in). Undocumented and fragile, but possible. -
Mobile: the staged mobile wiring (
src/app_runner/ts_core_mobile.zig) setshost_callsfrom 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_serviceare 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 exposingbinding()with the same contract as the desktop runner'shost_callsvalue, 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 nullfallback.
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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