Skip to content

Latest commit

 

History

History
838 lines (767 loc) · 63.2 KB

File metadata and controls

838 lines (767 loc) · 63.2 KB
title zig-js public C API
description Embed zig-js through its complete pinned JavaScriptCore-shaped public C target.

zig-js public C API

zig-js exports the complete checked-in JavaScriptCore-shaped public C target from c_api.zig. zig build installs the static library under zig-out/lib and compatible headers under zig-out/include/JavaScriptCore. Hosts that stay within the pinned target can link libzig-js.a in place of the system JavaScriptCore.framework and keep those documented calls unchanged.

The machine-readable macOS 27.0 inventory is the completion authority for the full checked-in declaration surface. The inventory currently has 117 implemented functions and zero pending entries. Use zig build c-api-audit for the fast drift gate or zig build test-c-api to compile, link, and execute both C and C++ embedding fixtures. zig build c-api-jsc-diff is the macOS-only semantic gate for completed value APIs against the hash-pinned system JavaScriptCore headers and framework.

The versioned consumer profiles apply a narrower second gate to real downstream source revisions. Home profile home-public-c-7ed99c02 pins 50 Zig C-ABI declarations and reports 50/50 exports with a real Zig compile-link-runtime fixture. It remains deliberately separate from private Home/Bun ABI work.

Private-profile exports are audited independently and never inflate the public or extension totals. The pinned Home inventory currently reports all 447 private symbols implemented; 25 consumer-provided declarations are tracked separately and rejected as duplicate zig-js exports. zig build test-home-private-abi, zig build test-private-jstype, and the feature-specific private ABI fixtures are their focused compile-link-runtime gates.

Response and Request preserve ReadableStream bodies, expose body and derived bodyUsed, tee live bodies during cloning, and route all Body conversions through the engine stream consumer. The exact pinned behavior is recorded in abi/fetch-body-lifecycle-407.json. WebAssembly streaming compilation reuses that lifecycle and the ordinary Wasm parser/linker; its exact MIME, Promise, realm, and error contract is recorded in abi/wasm-streaming-api-408.json. Extensions.h exposes the VM-affine create/finalize/release lifecycle around the pinned void JSC__Wasm__StreamingCompiler__addBytes feed; arbitrary tokens are never dereferenced, and finalization copies into caller storage. The pinned SQL row fast path has a real opaque Structure, shared object shapes, and bounds-checked GC-barriered offset writes; its exact ABI is recorded in abi/sql-object-structure-411.json. Private global creation, same-VM test isolation, execution-context identity transfer, precise old-realm reclamation, and terminal teardown are pinned by abi/global-object-lifecycle-412.json. Process-wide option initialization and the shell-timeout VM trap are pinned by abi/process-initialization-shell-timeout-417.json. The implemented surface covers JSC64 identity, cell equality, truthiness, int32 extraction, exact signed/unsigned 64-bit BigInt construction, modulo-2^64 BigInt extraction with the pinned int32/Int52 fallbacks, and exact JavaScript strict/SameValue equality across primitives and owned cells, plus profile-selected exact JSType queries and opaque BigInt downcast/order/signed extraction across arbitrary-size values, owned decimal BunString conversion, and exact JSCell/JSString downcast, UTF-16 length, 8-bit eligibility, equality, object access, and boxing. The BunString conversion family accepts 8/16-bit WTFStringImpl and every pointer-tagged ZigString encoding, preserves lone UTF-16 surrogates, truncates by code unit, consumes ownership only after successful transfer, and constructs ordered selected-realm arrays failure-atomically. Four ZigString error-instance exports create fresh Error, TypeError, RangeError, and SyntaxError objects with exact tagged-input messages, selected-realm intrinsic prototypes, and first-exception preservation. The ZigString DOMException export implements the complete pinned code matrix from 0 through 40: exact WebCore names, default messages, and legacy codes; Bun's intentional code-9 SyntaxError divergence; all RangeError, TypeError, ordinary Error, and undefined special branches; Node-style error codes; caller-message override; and the unknown-code empty-name DOMException fallback. It constructs against selected-realm intrinsics and preserves the first pending exception. Three AggregateError exports build fresh ordered error arrays or preserve an exact supplied array/cause, install standard non-enumerable descriptors, and read the own errors property directly. Same-VM siblings, foreign inputs, invalid arrays, failure atomicity, and first-exception behavior are covered. Eight property exports implement two-key selected-realm object construction, direct ZigString own writes, single-coercion property-key writes, ordinary deletion, ordinary prototype-aware reads, Bun's Object.prototype-cutoff lookup, and own-only BunString/value-key reads. They preserve numeric and Symbol keys, accessor/proxy behavior, duplicate-key insertion order, Latin-1 names, and the distinct empty/deleted/exception sentinels without an extra observable has trap. The separate property-path export implements the pinned UTF-16 dot/bracket parser and string/number array paths. It uses ToObject plus one ordinary Get per target segment, distinguishes absent values from present undefined, and preserves indexed path getters, proxy behavior, and abrupt completion without adding a has trap. Four class/display-name exports provide stable raw class metadata, constructor-derived calculated class names, function/internal names, and owned display names. They keep VM-inquiry class calculation non-invoking, perform one observable @@toStringTag read where the pinned name paths require it, and preserve exact borrowed ZigString versus owned UTF-16-capable BunString lifetimes. The received-value ErrorCode export additionally owns its BunString result and uses the exact Bun diagnostic inspector when an observable constructor lookup is falsy. It preserves null prototypes, descriptors, cycles, Proxy targets, custom inspection, and WTF-16 strings while suppressing constructor; abrupt completion and allocation failure publish no partial string. The normal and fast JSON exports share the full serializer but preserve the pinned numeric-indent versus undefined-space distinction. They retain observable toJSON/getter/proxy behavior, order and omission rules, exact Unicode escaping and error publication, and return independently owned Latin-1/UTF-16 BunString results. The native fromEntries and putRecord exports add exact copied ZigString record construction. They preserve selected-realm Object/Array prototypes, direct insertion, duplicate/integer-key ordering, zero/one/many cardinality, all-true own descriptors, and caller-buffer independence without invoking inherited setters. Invalid pointers, oversized counts, foreign cells, OOM, and pre-existing exceptions cannot publish a partial result. The value-key/BunString property trio performs one observable ToPropertyKey own-property inquiry, direct own data writes, and Bun's scalar-to-array upsert progression. Direct writes bypass prototype setters and replace configurable accessors; Symbol/index/proxy behavior, foreign-value rejection, and first pending exceptions remain exact. The JSX element predicate performs one ordinary $$typeof read and accepts only Symbol.for('react.element') or Symbol.for('react.transitional.element') from the VM-wide registry. It keeps inherited/getter/proxy observability and sibling-realm identity while rejecting local symbols, same-description values, primitives, and foreign VMs. The two core deep-equality exports provide Bun-pinned SameValue and cyclic structural comparison for enumerable strings/Symbols, arrays, Map/Set, buffers/views, numeric TypedArrays, boxed strings, Date, RegExp, and Error/cause state. Strict mode additionally distinguishes calculated classes, sparse holes, property counts, missing versus undefined, cause presence, and raw float bits. Getter/proxy failures, sibling realms, foreign cells, bounded recursion, and pending exceptions preserve the native boundary contract. The three Jest-aware exports add right-first asymmetric matching for anything/any, strings and regexes, array/object containment, numeric close-to, promise modes, negation, and custom marker hooks. Recursive subset matching uses inherited-aware single property reads, exact arrays, Symbols, independent cycle sets, nested object-containing exhaustiveness, and optional direct matched-property replacement. Ordinary deep equality remains hook-free. Five remote-inspector process controls use atomic, idempotent state for auto-start disable, explicit start, system-console logging, and the inspection default. The default is disabled on modern Apple-family targets and enabled elsewhere, and remains independently overrideable from context inspectability. The proxy internal-field export reads the target/handler slots without invoking traps, produces null for both after revocation, and rejects invalid field or non-proxy inputs. VM-wide canonical private object handles preserve exact raw EncodedJSValue identity when an existing object is projected again. Script execution contexts receive lazy, process-unique nonzero u32 IDs from an atomic allocator. IDs remain stable for a context, distinguish sibling realms, tolerate parallel independent creation, and leave pending exceptions untouched. The no-side-effects diagnostic projection renders every primitive, arbitrary BigInt, and Symbol exactly, returns an input string unchanged, and collapses all other objects to [object Object]. No conversion hook, getter, proxy trap, or mutable global is consulted, and existing pending exceptions survive. The rejection classifier performs JSC's exact own-stack descriptor check: accessors count without invoking their getters, inherited properties do not, and proxies receive one ordinary [[GetOwnProperty]] operation whose abrupt completion is published through the shared pending-exception boundary. The process-warning boundary converts strings and Error objects into queued, realm-local warning events with pinned name/code/detail metadata. Its unhandled-rejection companion emits the reason warning and Bun's exact warning Error in FIFO order, preserving throwing stack reads, fallback stringification, exception clearing, and listener failures. The process dispatch boundary emits exact unhandled/late-handled Promise events, orders the uncaught monitor before capture or ordinary handlers, preserves the pinned origins and handled return codes, and constructs the exact UnhandledPromiseRejection wrapper. Promise checkpoints suppress early-handled and duplicate notifications while retaining the original reason and Promise. The same realm-local event storage drives beforeExit and one-shot exit. Native signal delivery maps the pinned platform signal table to canonical SIG* event names and synchronously emits (name, number) on only the selected realm's process object; unknown signals and pending exceptions are inert. The private process next-tick calls enqueue into a separate realm FIFO with exact one- or two-argument invocation. Host checkpoints drain it before Promise jobs and repeat after the Promise phase when needed; precise roots cover queued and active batches, including resumable tails after an uncaught listener throws. IPC process-event shims gate decoding on listener presence, then synchronously deliver exact message(value, handle), error(value), or zero-argument disconnect events. Absent listeners are no-ops; observed foreign-VM values and listener failures publish through the normal pending-exception boundary. The iterable callback boundary performs ordered @@iterator, cached next, IteratorStep, and IteratorValue operations before forwarding stable encoded values and VM/global/context metadata. Callback-published exceptions close the open iterator while preserving the original failure over a throwing return(). The ZigString JSON bridge decodes all tagged forms into selected-realm parsed values. It returns and clears SyntaxError objects on invalid JSON, and returns a coded ERR_STRING_TOO_LONG Error before dereferencing an over-limit span. Three fast built-in reads pin every byte-table entry and distinguish direct data, own-slot, and Object.prototype-cutoff lookup. Bun's fourth core-only code inquiry is deliberately pure: inherited data is visible, while accessors, custom hooks, proxies, foreign cells, and absent properties are not. Three Symbol registry bridges convert tagged ZigString keys without userland coercion and return stable Latin-1/UTF-16 views for descriptions and registered keys. The GlobalSymbolRegistry is one object per C context group from realm creation onward; local/well-known symbols remain unregistered and VM boundaries remain isolated. Four additional string-construction exports copy every tagged ZigString form, validate raw UTF-8 conversion, canonicalize atom backing within one VM, and concatenate ordered ToString results with exact UTF-16 semantics. Source mutation, same-VM siblings, foreign values, abrupt completion, and a pre-existing exception are covered. Two borrowed ZigString output exports cache stable group-lifetime views as untagged Latin-1 or tagged UTF-16. Direct JSString conversion validates the cell and VM; JSValue conversion performs observable ToString and publishes Symbols, thrown values, foreign values, and allocation failure through the shared exception boundary. Five error-factory exports create fresh selected-realm Error, TypeError, and RangeError objects from ZigString message/code pairs and all BunString forms. They reproduce the pinned writable TypeError code and read-only RangeError code descriptors, omit empty codes, reject dead input, and preserve the first VM exception. They also cover fresh ordinary/null-prototype construction and exact boxed Number/String/Boolean/BigInt unwrapping, plus four-way BigInt/Number comparison, arbitrary-precision BigInt addition, and the pinned timeval-to-BigInt formula. Private ToObject/prototype queries preserve object identity, box every object-capable primitive kind in the selected realm, observe proxy/null prototypes, and reject nullish or foreign inputs. The numeric DateInstance pair creates fresh selected-realm Date cells without TimeClip and reads their raw internal doubles, including -0, NaN, infinities, and values outside the JavaScript Date-constructor range. Four Date parsing/ISO exports create fresh Date cells from NUL-terminated input, extract same-VM UTC epoch milliseconds, and write exact ordinary or extended UTC ISO text failure-atomically into 28-byte buffers. The Date-now writer shares the real Unix wall clock with Date.now(), Date(), and new Date(); its ABI follows Bun's executable wrapper/C++ contract because the pinned Zig declaration has an incompatible stale signature. The VM exception slice supplies one pending exception per shared context group, stable exception-cell identity, exact primitive/Error unwrapping, and has/clear/take/rethrow classification. The top-exception/termination slice uses the pinned caller-owned 8/56-byte scope layout, preserves VM identity across sibling realms, and distinguishes pure pending reads from trap-aware termination materialization. Termination requests and execution-forbidden state are atomic and VM-wide; selective clear retains the stable termination exception until explicit termination clear. Selected-realm OutOfMemoryError creation does not throw, while OOM and stack overflow throw helpers publish exact error kinds without replacing the first pending exception. Nine VM heap controls expose shared live, external, and saturating extra-memory accounting, deferred and full collection, weak-state processing, idle footprint reclamation, and positive-duration opportunistic job checkpoints. Precise heaps consume zig-gc's race-safe accounting snapshot and return the size after the completed full sweep; arena-backed VMs report their committed capacity. Seven job/registry controls add realm-local native and encoded microtasks, VM-wide live-realm draining, one-shot unhandled-rejection notification, exact per-realm module deletion, and safe delete-all-code. Native payloads execute once, encoded values must belong to the selected VM, reentrant jobs drain to quiescence, and a throwing job retains the untouched FIFO tail. Code deletion clears selected-realm module/source caches and blocks until every native execution or compilation lease retires before resetting tiers and unmapping pages; subsequent calls fall back safely and may recompile. Eight strong/weak reference exports retain the consumer-visible EncodedJSValue slot at offset zero, root exact same-VM values across collection, accept sibling realms, and reject foreign-VM replacement. Weak object targets use zig-gc atomic external slots, clear without retention, and deliver the FetchResponse owner callback once after GC clearing but never after explicit clear/delete. Wrappers retain their VM through deletion, and root-list mutation is synchronized with concurrent tracing. The array/index slice supplies exact logical-length and hole behavior, direct put/push/read operations that bypass inherited setters, ordinary indexed reads that observe prototypes and getters, VM exception publication for abrupt completion, sparse growth, and the u32 maximum-length boundary. The JSArray constructor pair adds failure-atomic packed construction from encoded slices and hole-only construction through the maximum u32 length, preserving selected-realm prototypes and same-VM sibling value identity while publishing foreign values and invalid lengths through the pending exception. The two contiguous-vector exports provide stable JSC64 snapshots for eligible packed arrays and require exact array/vector/length/backing/element revalidation before direct reads. Multiple snapshots coexist; GC preserves them, while mutation, holes, accessors, double/undecided storage, and indexed prototype pollution force the consumer back to ordinary indexed Get. ArrayBuffer__fromSharedMemfd validates and temporarily duplicates a caller descriptor, maps the declared complete regular-file extent read/write with MAP_PRIVATE, and exposes one overflow-checked subrange without copying as the profile-selected ArrayBuffer or Uint8Array type. Only the duplicate is closed; the caller descriptor remains owned by the caller. The mapping's idempotent owner unmaps the complete extent exactly once on failed construction, precise collection, or context teardown. Invalid descriptors, undersized files, ranges, and result tags return empty without publishing a partial value or pending exception. The private copy/allocation boundary creates isolated ArrayBuffers, allocates uninitialized Uint8Array storage for direct native filling, and preserves Bun's historical Bun__allocArrayBufferForCopy behavior: that name returns a Buffer-identified Uint8Array view. Its output pointer is written only after the view is complete. Default-allocator Uint8Array and ArrayBuffer constructors adopt non-empty mimalloc storage without copying and release it exactly once; zero-length sentinel pointers are never adopted. Private ToNumber performs the complete number-hint object coercion path, including user hooks and thrown conversions, preserves primitive and ordinary NaN behavior, throws for Symbol/BigInt, and retains the first VM exception. Private has-instance and iterator-method predicates preserve JSC's narrower internal prechecks, run ordinary/custom/proxy or object GetMethod behavior, and publish invalid prototypes, getters, and non-callable methods through the VM exception boundary. Private string inclusion applies ordered full ToString coercion and exact UTF-16 code-unit substring matching, including astral/surrogate boundaries and VM exception publication. Private class and AggregateError classification uses executable/native constructability and immutable error metadata, not mutable names or prototypes. Sibling C-API realms share well-known Symbols and the Symbol registry. Private Object keys/values create fresh selected-realm arrays in own enumerable string-key order. Keys do not read values; values execute getters in order, and proxy traps or thrown getters publish through the shared VM exception slot. String-wrapper indices are counted as UTF-16 code units. The 13 native StringBuilder exports preserve the pinned 24-byte/8-byte caller-owned storage contract and exact Latin-1/UTF-8/UTF-16 code units, including astral and lone-surrogate content. Integer and double formatting, JSON quoting, capacity overflow, non-destructive conversion, OOM publication, and first-pending-exception behavior match the pinned WebKit boundary. The two MarkedArgumentBuffer exports create a synchronous callback-only native container and root appended same-VM cells across precise collection until unconditional callback cleanup. Three CommonJS extension exports maintain a separate rooted value registry per realm with exact append, set, and swap-remove return behavior. The ten private Promise/InternalPromise exports create selected-realm pending or directly settled native promises, downcast exact Promise cells, preserve result/reason identity, and implement the pinned callback wrappers. JSPromise__wrap passes Promise results through and rejects callback Errors or throws; AnyPromise__wrap performs normal settlement, including thenable assimilation and self-resolution rejection. JSC__JSValue___then registers a detached native reaction pair and invokes only the selected JSHostFn at the next Promise checkpoint with (settlement value, retained context) in an exact JSC64 CallFrame. FIFO order, GC roots, sibling-realm identity, reentry, callback throws, and the pending-exception boundary are covered. The seven Home-only native Map operations bypass mutable userland prototypes and preserve SameValueZero keys, exact identity, insertion order, live size, sibling values, and failure-atomic foreign-VM rejection. The shared FFI slow paths perform exact signed/unsigned modulo-2^64 conversion for validated heap BigInts. CommonAbortReason conversion creates fresh selected-realm TimeoutError/AbortError DOMExceptions with the pinned messages and legacy codes. Three module-loader exports add persistent source registration, normalized relative/file resolution, cached namespace identity, exact Promise cells, the evaluate fulfilled/rejected/pending tri-state, top-level-await settlement, and precise module-graph roots. The JSString backing iterator adds exact Latin-1/UTF-16 callback delivery with caller-owned stop and lifetime semantics. JSFunction__createFromZig adds owned function metadata and exact JSC64 CallFrame delivery for call and construct callbacks, including pending-exception and foreign-return rejection. Three adjacent CallFrame exports expose the active callback's visible caller without changing its pinned register words: owned source URL, one-based line and column, exact builtin://bun/main recognition, and a thread-local NUL-terminated debug description. Identity/VM checks reject null, stale, mismatched, and foreign frames, while nested native reentry restores the outer descriptor. Four FFI-function exports reuse those callbacks with a distinct runtime brand. Names/arity are owned, call and construct share the pinned callback, nullable native data is atomically mutable, and the optional ptr property is the exact callback-address bit pattern with upstream read-only/enumerable/configurable attributes. Dynamic-library metadata remains separate from dataPtr; only validated FFI cells accept get/set, independent of their owning VM. The structured exception boundary retains creation-time frames separately from .stack and fills caller-owned Home/Bun buffers through the exact 48-byte trace, 72-byte frame, 12-byte position, and 216-byte exception layouts. Full projection classifies Error, DOMException, primitive, and system-like values; owns its error/system strings; preserves the stable exception cell; and reports the cause runtime type. The second pass resolves the retained script identity and copies the current source line plus capped preceding lines with exact zero-based numbers. Those source-line strings borrow an independently retained provider and survive collection or VM teardown; replacement and cross-thread final release are covered explicitly. The combined 392-symbol fixture also covers foreign-VM failures, exception clearing, callback reentrancy, and already-settled targets.

JSObjectCallAsFunctionReturnValueHoldingAPILock recursively holds the owning VM API lock, defaults a null receiver to the realm global, preserves argument order, and returns a thrown value as an exception cell without leaving a pending exception. JSObjectGetProxyTarget projects a live proxy's exact target without invoking traps and rejects non-proxies, revoked proxies, and non-value private handles. The Home and Bun compiled-consumer fixtures exercise both signatures and behaviors in Debug and ReleaseSafe.

Bun__JSC__operationMathPow and the JavaScript Math.pow builtin share one ECMAScript operation. It preserves JSC's NaN, infinity, signed-zero, negative-base, and odd/even exponent behavior instead of exposing host-libm exceptions at the private boundary.

The three async-context call exports snapshot a realm's active context into a branded, non-callable frame with precise GC edges. Calling the frame installs the captured value only for the callback's dynamic extent, restores the prior value before normal return or exception publication, preserves argument and receiver semantics, and never crosses VM ownership. With no active context the original callback is returned unchanged, including its exact encoded identity.

The ten DOMFormData exports use the engine's branded JS entry list as their single source of truth. They cover VM-scoped downcasts, empty and form-urlencoded construction, USVString append, Blob/File append with exact opaque native-pointer roundtrip, insertion-order callbacks, duplicate counts, and query serialization that omits files. The Home and Bun fixtures separately compile the pinned declarations and exercise native-created and JS-created instances.

The CommonStrings projection maps the pinned 13-value Bun enum to one stable encoded string cell per value per VM, shared by sibling realms but never by independent VMs.

The FetchHeaders projection shares one branded ref-counted record with the JavaScript Headers implementation. Its 21 core exports cover lifecycle, JS/native conversion, cloning, validated mutation/query, the 94-value fast-name enum, and checked sorted buffer projection. A 22nd export copies the pinned PicoHeaders {ptr,len} rows with raw HTTP-map duplicate semantics. The three opaque UWS/H3 adapters use an installed, version/size-checked v1 consumer table for the real C++ methods. Missing/failed request bridges return an empty handle; the response bridge receives Set-Cookie, known, then uncommon rows and applies the pinned TCP/SSL/H3 state rules.

Bun__attachAsyncStackFromPromise reconstructs stackless native Errors from pending async-await chains. Promise links retain only suspended activations, survive GC and settlement-to-microtask handoff, follow each transparent return/then forwarding segment for at most 32 hops, and stop at combinators or settled/invalid links. It preserves existing/materialized stacks and pending exceptions while honoring the selected realm's Error.stackTraceLimit.

Bun's separately pinned core src/jsc inventory reports 437 private symbols, all implemented by zig-js. Its source/signature audit is zig build bun-private-abi-audit; broader Bun runtime and generated bindings remain outside that core profile. Twenty-four additional declarations are defined by pinned Bun/Home C++ provider sources, while JSFunctionCall is emitted by each runtime-compiled FFI module. The provider contract rejects duplicate zig-js definitions and is final-link tested with zig build test-private-consumer-providers. Bun JSType numbering is selected with -Dprivate-abi-consumer=bun and verified by zig build test-private-jstype -Dprivate-abi-consumer=bun.

ZJSContextGetCollectionEpoch returns the monotonic count of explicit JSGarbageCollect calls for a context group. Every realm in the group observes the same epoch; arena-backed groups use it as a semantic weak-processing boundary even though physical storage remains allocated until VM teardown. ZJSValueIsReachable performs a quiescent, cycle-safe walk from every strong root in the group, including sibling realms, closures, environments, promises, and microtasks; WeakRef and weak-collection edges do not retain the queried value. It reports semantic reachability and does not reclaim storage.

JSC__JSGlobalObject__generateHeapSnapshot, Bun__generateHeapSnapshotV8, and Bun__generateHeapProfile reuse that exact VM-wide strong graph with property, index, variable, and internal edge labels. They publish WebKit GCDebugging v3, Chrome/V8 .heapsnapshot, and a complete Markdown cell/edge inventory respectively. The profile accounts ArrayBuffer backing bytes and computes retained sizes from the graph's dominator tree. Stable IDs survive repeated arena snapshots and precise-GC compaction; Home and Bun retain their distinct pointer and by-value string ownership ABIs. Run zig build test-private-heap-snapshot.

Bun__setSamplingInterval, Bun__startCPUProfiler, and Bun__stopCPUProfiler provide per-VM cooperative CPU sampling without a timer thread reading live frames. Chrome .cpuprofile output contains parent-sensitive nodes, samples, deltas, source positions, and position ticks; the Markdown form reports hot functions, call relationships, and files from the same samples. The interval is atomically published, restarting clears the prior session, and Home/Bun keep their distinct owned-string layouts. Run zig build test-private-cpu-profile.

Objective-C bridge target

The Objective-C compatibility target is separately pinned to macOS SDK 27.0 build 26A5368g. Its machine-readable inventory covers 11 interfaces, categories, and protocols plus 108 methods, properties, typedefs, data symbols, and macros from JSContext.h, JSValue.h, JSVirtualMachine.h, JSManagedValue.h, and JSExport.h. zig build objc-api-audit verifies the checked-in copies and inventory on every host; zig build test-objc-api-headers compiles the umbrella under the real macOS Objective-C ARC/blocks frontend. On macOS, zig build test-objc-api also compiles, links, and runs a host against the zig-js runtime classes. A live SDK comparison is:

python3 tools/verify-objc-api.py \
  --sdk-root "$(xcrun --sdk macosx --show-sdk-path)"

The inventory records 108 implemented / 0 pending declarations. Coverage includes VM/context construction, evaluation, C-ref wrapper identity, context metadata, primitive and native object factories, type predicates, numeric/string/geometry/Foundation conversion, comparisons, calls, construction, property operations, promise callback state, managed owner/value relations, exact wrapper identity, typed Objective-C blocks, JSExport instances/classes/renamed selectors, and property-descriptor symbols. The 18-row Objective-C transcript matches system JavaScriptCore exactly (360c1ad3ccf51d6b), including managed-owner behavior, same-VM cross-context value identity, exported receivers, constructors, prototypes, and target-context wrapper behavior. See the Objective-C bridge inventory guide for the exact boundary and reproduction details. The complete declared inventory does not claim compatibility with private JSC framework internals. Managed values clear semantically at explicit collection epochs while arena storage remains VM-lifetime. zig build test-objc-api-evidence batches header, runtime, system-JSC differential, 200-cycle lifetime/autorelease, ASan/UBSan, deterministic allocation/registration fault injection, and Apple leak-checker gates; the current leak result is 0 leaked bytes.

The project is still pre-stabilization. Compatibility-shaped entry points are an embedder convenience, not a promise to preserve inert arguments or incomplete JavaScriptCore behavior. When a compatibility shim conflicts with clear zig-js semantics, the shim should either grow real behavior or be redesigned before the API is declared stable.

Minimal embedding

#include <JavaScriptCore/JavaScript.h>

int main(void) {
  JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);

  JSStringRef script = JSStringCreateWithUTF8CString("40 + 2");
  JSValueRef result = JSEvaluateScript(ctx, script, NULL, NULL, 0, NULL);

  double n = JSValueToNumber(ctx, result, NULL);   // 42.0

  JSStringRelease(script);
  JSGlobalContextRelease(ctx);
  return 0;
}

Link against libzig-js.a instead of JavaScriptCore when your host only uses the implemented surface below.

Implemented surface

::: code-group

JSContextGroupRef  JSContextGroupCreate(void);
JSContextGroupRef  JSContextGroupRetain(JSContextGroupRef);
void               JSContextGroupRelease(JSContextGroupRef);
JSGlobalContextRef JSGlobalContextCreate(JSClassRef);
JSGlobalContextRef JSGlobalContextCreateInGroup(JSContextGroupRef, JSClassRef);
JSGlobalContextRef ZJSGlobalContextCreateThreaded(bool gil);
JSGlobalContextRef ZJSGlobalContextCreateGarbageCollected(bool enableJIT);
bool               ZJSContextRequestGarbageCompaction(JSContextRef);
typedef enum ZJSGCCompactionStatus {
    kZJSGCCompactionUnsupported,
    kZJSGCCompactionNoCandidates,
    kZJSGCCompactionOutOfMemory,
    kZJSGCCompactionCompacted,
} ZJSGCCompactionStatus;
ZJSGCCompactionStatus ZJSContextCompactGarbage(
    JSContextRef, size_t* movedCells, size_t* movedBytes);
JSGlobalContextRef JSGlobalContextRetain(JSGlobalContextRef);
void               JSGlobalContextRelease(JSGlobalContextRef);
JSObjectRef        JSContextGetGlobalObject(JSContextRef);
JSContextGroupRef  JSContextGetGroup(JSContextRef);
JSGlobalContextRef JSContextGetGlobalContext(JSContextRef);
JSStringRef        JSGlobalContextCopyName(JSGlobalContextRef);
void               JSGlobalContextSetName(JSGlobalContextRef, JSStringRef);
bool               JSGlobalContextIsInspectable(JSGlobalContextRef);
void               JSGlobalContextSetInspectable(JSGlobalContextRef, bool);
bool               JSCheckScriptSyntax(JSContextRef, JSStringRef source,
                                       JSStringRef sourceURL,
                                       int startingLineNumber, JSValueRef* exception);
JSValueRef         JSEvaluateScript(JSContextRef, JSStringRef source,
                                    JSObjectRef thisObject, JSStringRef sourceURL,
                                    int startingLineNumber, JSValueRef* exception);
void               JSGarbageCollect(JSContextRef);
JSType      JSValueGetType(JSContextRef, JSValueRef);
bool        JSValueIsUndefined(JSContextRef, JSValueRef);
bool        JSValueIsNull(JSContextRef, JSValueRef);
bool        JSValueIsBoolean(JSContextRef, JSValueRef);
bool        JSValueIsNumber(JSContextRef, JSValueRef);
bool        JSValueIsString(JSContextRef, JSValueRef);
bool        JSValueIsSymbol(JSContextRef, JSValueRef);
bool        JSValueIsBigInt(JSContextRef, JSValueRef);
bool        JSValueIsObject(JSContextRef, JSValueRef);
bool        JSValueIsObjectOfClass(JSContextRef, JSValueRef, JSClassRef);
bool        JSValueIsArray(JSContextRef, JSValueRef);
bool        JSValueIsDate(JSContextRef, JSValueRef);
JSTypedArrayType JSValueGetTypedArrayType(JSContextRef, JSValueRef, JSValueRef* exception);
bool        JSValueIsEqual(JSContextRef, JSValueRef, JSValueRef, JSValueRef* exception);
bool        JSValueIsStrictEqual(JSContextRef, JSValueRef, JSValueRef);
bool        JSValueIsInstanceOfConstructor(JSContextRef, JSValueRef,
                                           JSObjectRef constructor,
                                           JSValueRef* exception);
JSRelationCondition JSValueCompare(JSContextRef, JSValueRef, JSValueRef,
                                   JSValueRef* exception);
JSRelationCondition JSValueCompareInt64(JSContextRef, JSValueRef, int64_t,
                                        JSValueRef* exception);
JSRelationCondition JSValueCompareUInt64(JSContextRef, JSValueRef, uint64_t,
                                         JSValueRef* exception);
JSRelationCondition JSValueCompareDouble(JSContextRef, JSValueRef, double,
                                         JSValueRef* exception);
JSValueRef  JSValueMakeUndefined(JSContextRef);
JSValueRef  JSValueMakeNull(JSContextRef);
JSValueRef  JSValueMakeBoolean(JSContextRef, bool);
JSValueRef  JSValueMakeNumber(JSContextRef, double);
JSValueRef  JSValueMakeString(JSContextRef, JSStringRef);
JSValueRef  JSValueMakeSymbol(JSContextRef, JSStringRef description);
JSValueRef  JSBigIntCreateWithDouble(JSContextRef, double, JSValueRef* exception);
JSValueRef  JSBigIntCreateWithInt64(JSContextRef, int64_t, JSValueRef* exception);
JSValueRef  JSBigIntCreateWithUInt64(JSContextRef, uint64_t, JSValueRef* exception);
JSValueRef  JSBigIntCreateWithString(JSContextRef, JSStringRef, JSValueRef* exception);
JSValueRef  JSValueMakeFromJSONString(JSContextRef, JSStringRef);
JSStringRef JSValueCreateJSONString(JSContextRef, JSValueRef, unsigned indent,
                                    JSValueRef* exception);
bool        JSValueToBoolean(JSContextRef, JSValueRef);
double      JSValueToNumber(JSContextRef, JSValueRef, JSValueRef* exception);
int32_t     JSValueToInt32(JSContextRef, JSValueRef, JSValueRef* exception);
uint32_t    JSValueToUInt32(JSContextRef, JSValueRef, JSValueRef* exception);
int64_t     JSValueToInt64(JSContextRef, JSValueRef, JSValueRef* exception);
uint64_t    JSValueToUInt64(JSContextRef, JSValueRef, JSValueRef* exception);
JSStringRef JSValueToStringCopy(JSContextRef, JSValueRef, JSValueRef* exception);
JSObjectRef JSValueToObject(JSContextRef, JSValueRef, JSValueRef* exception);
void        JSValueProtect(JSContextRef, JSValueRef);
void        JSValueUnprotect(JSContextRef, JSValueRef);
bool        ZJSValueProtect(JSContextRef, JSValueRef);   // zig-js extension
bool        ZJSValueUnprotect(JSContextRef, JSValueRef); // zig-js extension
JSClassRef  JSClassCreate(const JSClassDefinition*);
JSClassRef  JSClassRetain(JSClassRef);
void        JSClassRelease(JSClassRef);
JSObjectRef JSObjectMake(JSContextRef, JSClassRef, void* data);
JSValueRef  JSObjectGetPrototype(JSContextRef, JSObjectRef);
void        JSObjectSetPrototype(JSContextRef, JSObjectRef, JSValueRef);
bool        JSObjectHasProperty(JSContextRef, JSObjectRef, JSStringRef);
bool        JSObjectHasPropertyForKey(JSContextRef, JSObjectRef, JSValueRef key,
                                      JSValueRef* exception);
JSValueRef  JSObjectGetPropertyForKey(JSContextRef, JSObjectRef, JSValueRef key,
                                      JSValueRef* exception);
void*       JSObjectGetPrivate(JSObjectRef);
bool        JSObjectSetPrivate(JSObjectRef, void* data);
JSObjectRef JSObjectMakeArray(JSContextRef, size_t argc, const JSValueRef args[], JSValueRef* exception);
JSObjectRef JSObjectMakeDate(JSContextRef, size_t argc, const JSValueRef args[], JSValueRef* exception);
JSObjectRef JSObjectMakeError(JSContextRef, size_t argc, const JSValueRef args[], JSValueRef* exception);
JSObjectRef JSObjectMakeRegExp(JSContextRef, size_t argc, const JSValueRef args[], JSValueRef* exception);
JSObjectRef JSObjectMakeFunction(JSContextRef, JSStringRef name, unsigned parameterCount,
                                 const JSStringRef parameterNames[], JSStringRef body,
                                 JSStringRef sourceURL, int startingLineNumber,
                                 JSValueRef* exception);
JSObjectRef JSObjectMakeDeferredPromise(JSContextRef, JSObjectRef* resolve,
                                        JSObjectRef* reject, JSValueRef* exception);
JSValueRef  JSObjectGetProperty(JSContextRef, JSObjectRef, JSStringRef name, JSValueRef* exception);
void        JSObjectSetProperty(JSContextRef, JSObjectRef, JSStringRef name,
                                JSValueRef value, JSPropertyAttributes, JSValueRef* exception);
void        JSObjectSetPropertyForKey(JSContextRef, JSObjectRef, JSValueRef key,
                                      JSValueRef value, JSPropertyAttributes,
                                      JSValueRef* exception);
JSValueRef  JSObjectGetPropertyAtIndex(JSContextRef, JSObjectRef, unsigned index,
                                       JSValueRef* exception);
void        JSObjectSetPropertyAtIndex(JSContextRef, JSObjectRef, unsigned index,
                                       JSValueRef value, JSValueRef* exception);
bool        JSObjectDeleteProperty(JSContextRef, JSObjectRef, JSStringRef,
                                   JSValueRef* exception);
bool        JSObjectDeletePropertyForKey(JSContextRef, JSObjectRef, JSValueRef key,
                                         JSValueRef* exception);
JSValueRef  JSObjectCallAsFunction(JSContextRef, JSObjectRef, JSObjectRef thisObject,
                                   size_t argc, const JSValueRef args[], JSValueRef* exception);
JSObjectRef JSObjectMakeFunctionWithCallback(JSContextRef, JSStringRef name,
                                             JSObjectCallAsFunctionCallback callback);
JSObjectRef JSObjectCallAsConstructor(JSContextRef, JSObjectRef constructor,
                                      size_t argc, const JSValueRef args[], JSValueRef* exception);
bool        JSObjectIsFunction(JSContextRef, JSObjectRef);
bool        JSObjectIsConstructor(JSContextRef, JSObjectRef);
JSObjectRef JSObjectMakeTypedArray(JSContextRef, JSTypedArrayType, size_t length,
                                   JSValueRef* exception);
JSObjectRef JSObjectMakeTypedArrayWithBytesNoCopy(
    JSContextRef, JSTypedArrayType, void* bytes, size_t byteLength,
    JSTypedArrayBytesDeallocator, void* deallocatorContext, JSValueRef* exception);
JSObjectRef JSObjectMakeTypedArrayWithArrayBuffer(JSContextRef, JSTypedArrayType,
                                                  JSObjectRef buffer, JSValueRef* exception);
JSObjectRef JSObjectMakeTypedArrayWithArrayBufferAndOffset(
    JSContextRef, JSTypedArrayType, JSObjectRef buffer, size_t byteOffset,
    size_t length, JSValueRef* exception);
void*       JSObjectGetTypedArrayBytesPtr(JSContextRef, JSObjectRef, JSValueRef* exception);
size_t      JSObjectGetTypedArrayLength(JSContextRef, JSObjectRef, JSValueRef* exception);
size_t      JSObjectGetTypedArrayByteLength(JSContextRef, JSObjectRef, JSValueRef* exception);
size_t      JSObjectGetTypedArrayByteOffset(JSContextRef, JSObjectRef, JSValueRef* exception);
JSObjectRef JSObjectGetTypedArrayBuffer(JSContextRef, JSObjectRef, JSValueRef* exception);
JSObjectRef JSObjectMakeArrayBufferWithBytesNoCopy(
    JSContextRef, void* bytes, size_t byteLength, JSTypedArrayBytesDeallocator,
    void* deallocatorContext, JSValueRef* exception);
void*       JSObjectGetArrayBufferBytesPtr(JSContextRef, JSObjectRef, JSValueRef* exception);
size_t      JSObjectGetArrayBufferByteLength(JSContextRef, JSObjectRef, JSValueRef* exception);
JSStringRef JSStringCreateWithCharacters(const JSChar* characters, size_t length);
JSStringRef JSStringCreateWithUTF8CString(const char* string);
JSStringRef JSStringRetain(JSStringRef);
void        JSStringRelease(JSStringRef);
size_t      JSStringGetLength(JSStringRef);
const JSChar* JSStringGetCharactersPtr(JSStringRef);
size_t      JSStringGetMaximumUTF8CStringSize(JSStringRef);
size_t      JSStringGetUTF8CString(JSStringRef, char* buffer, size_t bufferSize);
bool        JSStringIsEqual(JSStringRef, JSStringRef);
bool        JSStringIsEqualToUTF8CString(JSStringRef, const char*);
JSWorkerRef JSWorkerCreate(JSStringRef source);
JSWorkerRef JSWorkerCreateWithLimits(JSStringRef source,
                                     size_t maxMessageBytes,
                                     size_t maxQueuedBytes,
                                     size_t maxQueuedMessages);
bool        JSWorkerPostMessage(JSWorkerRef, JSContextRef, JSValueRef, JSValueRef* exception);
JSValueRef  JSWorkerReceive(JSWorkerRef, JSContextRef, uint64_t timeoutMs, JSValueRef* exception);
void        JSWorkerTerminate(JSWorkerRef);
void        JSWorkerRelease(JSWorkerRef);
bool        ZJSWorkerGetInspectorTargetInfo(JSWorkerRef, ZJSInspectorTargetInfo* info);
ZJSWorkerInspectorSessionRef ZJSWorkerInspectorSessionCreate(
    JSWorkerRef, ZJSInspectorMessageCallback, void* userData);
bool        ZJSWorkerInspectorSessionDispatch(
    ZJSWorkerInspectorSessionRef, const char* message, size_t messageLength);
ZJSWorkerInspectorPumpResult ZJSWorkerInspectorSessionPump(
    ZJSWorkerInspectorSessionRef, uint64_t timeoutMs);
void        ZJSWorkerInspectorSessionRelease(ZJSWorkerInspectorSessionRef);

:::

Native callbacks use the standard JSObjectCallAsFunctionCallback calling convention, so functions you expose to JavaScript through this subset are registered exactly as they are with JavaScriptCore. JSObjectIsConstructor uses the runtime's constructability check, including native constructors such as Date and Array. JSObjectMakeArray returns a real runtime Array object in the current realm, inheriting from that realm's Array.prototype. JSObjectGetProperty and JSObjectGetPropertyAtIndex perform JavaScript [[Get]], including prototype lookup, accessor/proxy behavior, and exception reporting. JSObjectSetProperty maps ReadOnly, DontEnum, and DontDelete attributes to JavaScript writable, enumerable, and configurable descriptor fields. JSValueIsEqual performs JavaScript abstract equality (==), including object coercion and exception reporting. JSValueGetType reports Symbol primitives as symbol and BigInt primitives as the zig-js bigint extension instead of leaking the engine's object-tagged representation. JSValueToNumber matches Number(value), including primitive/boxed BigInt conversion and exception reporting for throwing coercions or Symbols. JSValueToStringCopy performs JavaScript ToString, including object coercion and exception reporting for throwing coercions or Symbol values. JSValueToObject performs JavaScript ToObject conversion, returning real primitive wrapper objects and reporting an exception for null / undefined. JSValueIsDate reports the runtime's Date internal slot, including invalid Date objects. ZJSGlobalContextCreateThreaded, ZJSGlobalContextCreateGarbageCollected, ZJSContextRequestGarbageCompaction, ZJSContextCompactGarbage, and JSWorker* are zig-js extensions rather than public JSC symbols. Direct compaction requires a quiescent precise-GC context. ZJSContextRequestGarbageCompaction only schedules movement, so it is safe inside a native callback; after that frame unwinds, the current movement-safe baseline checkpoint or a direct quiescent call consumes it. Generic live native frames and conservative-stack modes remain fail-closed. Direct attempts report unsupported, no_candidates, out_of_memory, or compacted exactly and zero-initialize optional movement outputs.

A JSValueRef kept across a scheduled movement boundary must be rooted with ZJSValueProtect/ZJSValueUnprotect.

JSContextGroupCreate owns one shared arena heap and executable-code lifetime. Each JSGlobalContextCreateInGroup call creates a distinct realm with its own global object, intrinsics, lexical environment, shapes, and microtask queue. Values retain identity and may cross between realms in the same group; another group is rejected deterministically. Every global context retains its group, so releasing a realm handle cannot invalidate a value already retained by a peer realm. The final group/context release tears down every realm and the shared VM once. A non-null JSClassRef is attached to the actual global object, including static members and initialize/finalize lifetime callbacks.

The zig-js extension header exposes ZJSInspectorSessionCreate, ZJSInspectorSessionDispatch, and ZJSInspectorSessionRelease for the versioned in-process JSON transport documented in Inspector protocol. All successful C-API, eval, generated-function, and JavaScript-module parses publish stable script IDs and exact adjusted statement locations. debugger; and explicit pause requests stop at those boundaries; URL/script breakpoints resolve to the nearest following statement and may be removed deterministically. Step-into, step-over, and step-out use ordinary-function logical call depth. The synchronous callback must issue a continuation before returning. Exception policy distinguishes caught-origin pauses from throws that actually escape JSEvaluateScript. Suspendable generator/async VM chunks carry the same statement checkpoints across yield/await. Debug-parsed ordinary functions use the tree walker; historical bytecode retains latent checkpoints, and attachment withholds native entry so a warmed function cannot bypass a pause. Ordinary VM frame slots are exposed as a live named scope and synchronized after debugger evaluation. Paused events expose live call frames, function/source locations, this, and lexical through global scope chains. Debugger.evaluateOnCallFrame evaluates against a selected live environment and may update its bindings before resume. Expandable remote-object handles expose own data/accessor descriptors without invoking getters. Handles are session-owned, group-releasable, protected across GC, and invalidated on explicit release/session teardown; scope handles also expire on resume. Worker target transport remains tracked by inspector issue #156. When several debugger sessions are enabled, observers receive the paused state first and only the deterministic owner may resume or step; explicit pause/step requests retain that ownership. ZJSInspectorSessionRelease is safe from within a protocol callback: physical teardown is deferred until the enclosing dispatch/pause/detach operation has unwound, and releasing the pause owner acts as a deterministic resume.

JSWorkerCreate* does not take a parent JSContextRef; each worker owns an independent runtime on its own thread. A context inspector therefore never claims those workers as implicit child targets. Parent pauses leave worker message processing and termination live (the C transcript performs both from the paused callback). Every worker already publishes a process-wide non-zero target ID, script/module kind, and atomic starting, running, closing, or closed lifecycle snapshot through ZJSWorkerGetInspectorTargetInfo. ZJSWorkerInspectorSession* attaches to that isolated runtime, queues commands to its owner thread, and returns events through an explicit owner-side pump. Session release waits until runtime-side backend/root cleanup completes, with an embedded allocation-independent detach fallback if the command queue cannot grow. Worker-first release closes accepted pending traffic and leaves a session safe to pump to closed and release afterward. The completed evidence matrix is recorded in issue #156.

JSGlobalContextRetain and JSGlobalContextRelease maintain a real C-API reference count for contexts created through this C API. Releasing a retained context destroys the underlying runtime only after the final release. JSGlobalContextRetain returns null for a null context or if retaining would overflow the context refcount.

The typed-array API uses the public JavaScriptCore enum layout through BigUint64Array. JavaScript Float16Array remains available inside the engine, but the pinned public JSC enum has no Float16 entry, so JSValueGetTypedArrayType reports kJSTypedArrayTypeNone for that runtime-only kind. ArrayBuffer-backed constructors preserve the original buffer and requested view geometry; invalid types return null, while detached, out-of-bounds, misaligned, overflowing, wrong-context, and non-ArrayBuffer inputs report through the exception out pointer.

The pointers returned by JSObjectGetTypedArrayBytesPtr and JSObjectGetArrayBufferBytesPtr are borrowed and temporary. They may be invalidated by later engine calls that detach, transfer, resize, or collect the backing object. Hosts must keep the corresponding JS object reachable and must synchronize their own native access with JavaScript execution.

The no-copy constructors transfer backing-store lifetime to the context. Their deallocator is invoked exactly once: immediately when construction fails, from object finalization in GC-enabled contexts, or during context teardown in arena contexts. A successful zero-length no-copy buffer may use a null byte pointer; a non-empty buffer requires a non-null pointer.

JSEvaluateScript rejects a null source string by returning null and reporting an exception through the out pointer when one is provided. For parse/lex failures, the exception is a SyntaxError object whose message includes the source name and adjusted line/column; the object also carries non-enumerable sourceURL, line, column, and byteOffset properties for embedders that do not want to parse message text. For runtime throws of Error objects, sourceURL and startingLineNumber are attached as non-enumerable properties, and the default stack string includes that source frame when present.

When an exception-capable API has produced a successful JavaScript result but cannot allocate the C JSValueRef / JSStringRef wrapper needed to return it, it reports OutOfMemory through the exception out pointer instead of returning an ambiguous silent null.

JSObjectGetProperty, JSObjectGetPropertyAtIndex, and JSObjectSetProperty reject null object refs and null property-name strings by reporting an exception through the out pointer.

JSValueIsEqual, JSValueToNumber, JSValueToStringCopy, and JSValueToObject reject null value refs by reporting an exception through the out pointer.

JSValueRef / JSObjectRef handles are owned by the context that created them. APIs that receive a JSContextRef reject handles from a different context instead of mixing arenas or object graphs: exception-capable APIs report a TypeError, while no-exception inspection/protection APIs return their invalid-handle result.

For no-exception value inspection APIs, a null or wrong-context value ref is an invalid handle, not JavaScript undefined: JSValueGetType returns the zig-js extension invalid, value predicates and JSValueIsStrictEqual return false, and JSValueToBoolean returns false.

Public JSValueProtect and JSValueUnprotect have JavaScriptCore's void ABI. The ZJSValueProtect and ZJSValueUnprotect extensions return whether the handle-table operation was accepted; they report false for invalid/null handles, missing protected entries on GC-enabled contexts, allocation failure, or protection-count overflow.

JSObjectSetProperty and JSWorkerPostMessage reject null value refs by reporting an exception through the out pointer instead of storing or posting JavaScript undefined.

JSObjectMakeArray, JSObjectCallAsFunction, and JSObjectCallAsConstructor reject null argv arrays when argc > 0 and null value refs inside non-null argument arrays by reporting an exception through the out pointer.

JSValueMakeString rejects a null string ref by returning null instead of creating JavaScript undefined.

JSStringCreateWithUTF8CString accepts valid UTF-8 only. A null pointer or invalid UTF-8 byte sequence returns null, so later string APIs can use the validated UTF-8 backing safely.

JSStringRetain returns null for a null string ref or if retaining would overflow the string refcount; successful retains must still be paired with JSStringRelease.

Native callbacks installed with JSObjectMakeFunctionWithCallback must return a non-null value ref or set the exception out pointer; returning null without an exception throws a TypeError instead of implicitly producing JavaScript undefined.

JSObjectCallAsFunction(..., thisObject, ...) uses the provided object as the call receiver, or the context global object when thisObject is null.

JSObjectCallAsConstructor performs the runtime [[Construct]] path and reports constructor throws through the exception out pointer.

JSObjectMakeFunctionWithCallback returns null when the callback pointer is null.

JSObjectMakeDate, JSObjectMakeError, and JSObjectMakeRegExp invoke the realm's retained intrinsic constructors, so replacing the corresponding global binding does not change their behavior. JSObjectMakeFunction likewise uses the intrinsic Function constructor, preserves the requested name and source text, and annotates syntax exceptions with the supplied source URL and starting line. JSObjectSetPropertyForKey performs ToPropertyKey exactly once, supports Symbols and coerced primitive/object keys, propagates coercion/internal-method exceptions, and applies JSC property attributes to newly created own properties.

JSClassCreate deep-copies its definition, static tables, and names, retains its parent, and uses an atomic reference count independent of the caller's definition storage. JSObjectMake(..., class, data) retains the class for the object lifetime, runs inherited initializers parent-first, finalizers child-first, and keeps the opaque pointer as host-owned private data. Automatic classes share a per-context, GC-rooted prototype carrying their static functions; NoAutomaticPrototype classes receive distinct own function objects, matching JSC. Static values use a tri-state internal-method bridge: null getter results remain absent, handled setters consume a write, declined setters do not accidentally create a data property, and DontDelete controls the delete result while class-defined values remain virtual. Reflection exposes JSC-compatible static-value descriptors and key membership; zig-js deliberately returns declared static names in deterministic child-first definition order instead of exposing JSC's internal hash-table iteration order. Every JSClassDefinition callback family runs through the interpreter: inherited dynamic properties and property names, call/construction, hasInstance, and number/string conversion all use JSC's handled/fallback and exception rules. JSObjectCopyPropertyNames returns a retained, deduplicated snapshot of enumerable names across the prototype chain; callback-added names participate in Object.keys and Reflect.ownKeys exactly as in JSC. JSObjectMakeConstructor creates JSC-compatible construct-only objects, retains the instance class through GC, and either invokes its explicit constructor callback or creates a class instance by default. Callback results and exceptions are realm-validated: foreign-context handles, null required results, non-object constructor results, and object-valued primitive conversions become deterministic local exceptions. JSValueIsObjectOfClass recognizes both the exact class and retained ancestors. JSObjectGetPrivate returns only host-owned private data; engine-owned native records are not exposed. JSObjectSetPrivate can update host-owned private data and can attach host data to plain objects that do not already carry engine private data.

JSObjectGetPrototype and JSObjectSetPrototype use the runtime's real [[GetPrototypeOf]]/[[SetPrototypeOf]] paths, including Proxy traps, invariants, cycle prevention, and null prototypes. Their pinned JSC signatures have no exception out pointer, so rejected/throwing mutations leave the object unchanged. JSObjectHasProperty, JSObjectSetProperty, JSObjectDeleteProperty, and indexed writes likewise use the engine's internal-method funnels rather than bypassing class callbacks, proxies, accessors, typed-array rules, or property attributes.

JSObjectMakeDeferredPromise returns a pending native Promise and stores callable resolve/reject functions in the required out pointers. Passing a null resolve or reject out pointer is a contract error reported through the exception out pointer. The returned functions settle the promise through the normal Promise job queue; embedder-observable callbacks run at the next microtask checkpoint, such as the one performed after JSEvaluateScript.

JSWorkerPostMessage and JSWorkerReceive use structured clone to move values between isolated worker contexts. Each worker uses an independent precise GC heap by default, so long-lived agents reclaim unreachable values before join. JSWorkerCreate uses the default 64 MiB per-message, 256 MiB queued-byte, and 1024 queued-message caps in both directions; JSWorkerCreateWithLimits sets all three explicitly (zero is a real zero limit). The message cap includes frame/manifest overhead and is enforced during serialization. Rejected closed/full/oversized delivery returns false and reports an exception instead of silently dropping a frame. JSWorkerRef handles are owner-thread-affine: post, receive, terminate, release, and inspector operations must be called on the thread that created the worker. Null or foreign-thread worker refs are rejected; exception-capable worker APIs report through the exception out pointer. Values that structured clone rejects, such as functions and Symbols, also report through the exception out pointer. Inspector target IDs are process-wide non-zero integers and never encode a worker address; exhaustion fails worker creation instead of reusing an identity. Worker inspector dispatch is asynchronous: accepted JSON is copied into a synchronized command queue, executed only by the worker runtime, and its response/events are copied back. ZJSWorkerInspectorSessionPump invokes one callback on the worker-handle owner thread (timeoutMs == 0 waits indefinitely) and reports message, timeout, or closed explicitly. A paused worker keeps servicing this queue, so the pump callback may dispatch evaluation or its continuation without accessing the worker Context directly.

JSStringCreateWithUTF8CString(null) returns null. JSStringGetUTF8CString returns 0 for null strings, null output buffers, or zero buffer size; otherwise it writes a null-terminated UTF-8 prefix and returns the number of bytes written including the terminator.

Caveats

Warning

The complete pinned macOS 27.0 public C inventory is implemented. This does not imply compatibility with Objective-C JSValue/JSContext, WebKit private internals, or the still-growing pause/breakpoint/stepping portion of the zig-js inspector protocol; those remain tracked by the umbrella roadmap and inspector issue. The language/runtime scope is whatever the configured conformance runner currently proves — see Conformance.

Some functions intentionally keep JavaScriptCore-shaped signatures while zig-js is still pre-stabilization, but the documented parameters now either have real behavior or fail fast when the underlying feature is out of scope. JSEvaluateScript honors thisObject, uses sourceURL / startingLineNumber for syntax and runtime Error metadata, and parser-created SyntaxErrors expose non-enumerable line/column diagnostics instead of requiring callers to parse message text. JSGlobalContextRetain / JSGlobalContextRelease maintain a real C-API reference count, JSObjectMakeFunctionWithCallback honors the provided function name, and JSObjectSetProperty honors property attributes.

Threading. Handles are affine to the thread that owns their context group. Values may cross global contexts in the same group while retaining identity; different groups are rejected. JSWorkerRef and inspector-session handles are also owner-thread-affine. Use worker messages, not worker handles, as the cross-thread boundary; see the threading docs.