yolk

The bridge

The bridge is the layer between the JavaScript runtime and native code. Yolk's bridge is a true zero-copy, high-performance interface designed for shared memory access and end-to-end type safety.

True Zero-Copy Architecture

Unlike traditional bridges that copy data at the boundary, Yolk shares the same raw memory between Swift and JavaScript.

How it works

  1. JS to Swift: When JS passes an ArrayBuffer, Yolk extracts its C-pointer and wraps it in a Swift Data object using Data(bytesNoCopy:...). We manage memory lifetime by capturing the JSValue in the Data deallocator, preventing garbage collection until Swift releases the buffer.
  2. Swift to JS: When Swift passes Data to JS, we bridge it to NSData and pass its raw bytes to JSObjectMakeArrayBufferWithBytesNoCopy. JS sees this as a standard ArrayBuffer pointing directly to Swift's memory.

Why this matters

  • Zero Serialization: Eliminates JSON.stringify and JSON.parse entirely.
  • Shared Memory: Perfect for high-bandwidth tasks like image processing, audio streaming, or real-time physics logic.
  • Safe Value Semantics: Swift Data uses Copy-on-Write (CoW). If Swift tries to mutate JS-owned memory, it automatically creates a local copy, ensuring thread safety and preventing memory corruption on the JS heap.

How a call works

Arguments are packed into a binary buffer using the YolkBin protocol and passed via shared memory:

TypeScript                    Bridge                     Swift
──────────                    ──────                     ─────
playground.increment(1)
  │
  ├─▶ YolkBin.encode([1]) ──▶ ArrayBuffer (Shared Ptr)
  │                               │
  └─▶ __yolk_native_Playground("increment", buffer)
           │                      │
           ├─▶ Create JS Promise  │
           │                      │
           └─▶ Task {             ▼
                 // args is raw Data pointing to JS memory
                 await module.handle("increment", args)
                     │
                     ├─▶ YolkBin.decode(args) ──▶ [1.0]
                     │
                     └─▶ actor PlaygroundModule
                               │
                               └─▶ increment(step: 1.0) ──▶ PlaygroundState(...)
                 }
                     │
                     ├─▶ YolkBin.encode(state) ──▶ Data (Shared Ptr)
                     │                               │
                     └─▶ jsQueue.async {             ▼
                               // result is ArrayBuffer pointing to Swift memory
                               __yolk.resolve(42, result)
                               drainMicrotasks()       ──▶ Promise resolves
                         }

The YolkBin Protocol

Yolk implements a custom Type-Length-Value (TLV) protocol for binary serialization. It handles primitives, objects, and zero-copy raw buffers:

  • 0x03 (Number): Native Float64 (8 bytes).
  • 0x04 (String): UTF-8 bytes with a length prefix.
  • 0x07 (Buffer): Direct pointer sharing for ArrayBuffer and Data.

Single Source of Truth (SSOT)

Yolk follows a reactive state model. The application state lives in JavaScript. Whenever the state mutates, the JS logic pushes the new state as a binary buffer to a native Observer:

// JS notifies native of state change via zero-copy buffer
const buffer = YolkBin.encode([state]);
globalThis.__yolk_native_Observer("onStateChanged", buffer);

On the native side, the generated PlaygroundState struct is automatically updated, triggering a reactive UI refresh.