Skip to content

v8 stack 2/5: TurboModule spec and the JS surface - #24

Closed
dmurphy5 wants to merge 1 commit into
dylan/v8-stack-1-foundationfrom
dylan/v8-stack-2-js-api
Closed

v8 stack 2/5: TurboModule spec and the JS surface#24
dmurphy5 wants to merge 1 commit into
dylan/v8-stack-1-foundationfrom
dylan/v8-stack-2-js-api

Conversation

@dmurphy5

@dmurphy5 dmurphy5 commented Jul 27, 2026

Copy link
Copy Markdown

The stack

Five PRs, each based on the one before it. This is 2/5.

PR Contents Base
1 #23 toolchain, dependencies, and the ai.openspace rename master
2 #24 TurboModule spec and the JS surface 👈 #23
3 #25 Android durable journal and TurboModule #24
4 #26 iOS Swift TurboModule rewrite #25
5 #27 docs and example app #26

#22 stays open and untouched as the single-diff view of the same change.
Its tree and this stack's tip are byte-identical (tree f8404b0), so merging
either one produces exactly the same master. Review whichever is easier to
follow; merge one and close the other.


Defines the TurboModule spec and the v8 JS surface. This PR lands the contract
— the natives that back it arrive in the next two PRs in the stack.

What's in it

src/NativeRNFileUploader.ts is the codegen spec both platforms generate from.
Six methods and five event emitters replace the hand-rolled NativeModules
lookup and the DeviceEventEmitter string channels.

Variant payloads (upload options, terminal events, snapshots) are typed as
UnsafeObject in the spec because codegen can't model Partial<>,
intersections, or index signatures. The precise contracts live in src/types.ts
and are applied at the public boundary in src/index.ts, so callers still get
full types — the looseness stops at the codegen layer.

Three new methods make terminal outcomes recoverable rather than
fire-and-forget: getUnacknowledgedEvents/ackEvents read and drain the native
journal, and getAllUploads enumerates what the OS still knows about. Terminal
events carry eventId, type, timestamp, and — where the platform has them —
responseCode, responseBody, responseHeaders.

Two behavior fixes worth a look:

  • A scoped addListener now drops events it can't attribute, instead of failing
    open and delivering another upload's event to your handler.
  • The iOS keep-alive hack that called native addListener on import is gone;
    the codegen emitters need no priming.

Adds jest and the first JS test suite, wired into CI and the pre-commit hook.

Reviewing this in the stack

The JS here calls TurboModuleRegistry.getEnforcing, so the new methods have no
implementation until PRs 3 and 4 land. That's inherent to slicing this way —
codegen reads the spec, so the spec has to come before the natives that extend
it. The stack is runtime-complete at its tip.

Verified

yarn typecheck, yarn lint:ci, and yarn test (7/7) pass on this commit.

Breaking

Requires the New Architecture. completed now fires only for 2xx or a request's
acceptStatus; other HTTP responses arrive as error with errorKind: 'http'
and the response attached.

Adds src/NativeRNFileUploader.ts, the codegen spec both platforms generate
from. Six methods and five event emitters replace the hand-rolled
NativeModules lookup and the DeviceEventEmitter string channels.

Variant payloads (upload options, terminal events, snapshots) are typed as
UnsafeObject in the spec because codegen cannot model Partial<>,
intersections, or index signatures. The precise contracts live in src/types.ts
and are applied at the public boundary in src/index.ts, so callers still get
full types.

Three new methods make terminal outcomes recoverable rather than fire-and-
forget: getUnacknowledgedEvents/ackEvents read and drain the native journal,
and getAllUploads enumerates what the OS still knows about. Terminal events
carry eventId, type, timestamp, and — where the platform has them —
responseCode, responseBody, and responseHeaders.

Two behavior fixes on the JS side. A scoped addListener now drops events it
cannot attribute instead of failing open and delivering another upload's
event, and the iOS keep-alive hack that called native addListener on import
is gone, since the codegen emitters need no such priming.

Adds jest with the first test suite for the JS layer, and wires it into CI and
the pre-commit hook.

This PR lands the contract only. The Android and iOS implementations that
back getUnacknowledgedEvents, ackEvents, getAllUploads, and the codegen
emitters arrive in the next two PRs in this stack.

BREAKING CHANGE: requires the New Architecture. `completed` now fires only for
2xx or a request's acceptStatus; other HTTP responses arrive as `error` with
errorKind 'http' and the response attached.

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