# Interop.md — U ↔ PHP (Zend) and U ↔ JavaScript (V8/N-API)

## The problem

Not every PHP or TypeScript function can be transpiled to pure U. Some functions are thin wrappers around the runtime engine itself — `PDO`, `curl_exec`, `preg_match` (PCRE), `openssl_encrypt` on the PHP side; `fetch`, `setTimeout`, `Buffer`, `crypto.subtle` on the JS side. These need the actual runtime to execute. U must be able to call into Zend and V8 at the C level, pass values back and forth, and handle the lifecycle differences (GC vs ARC, event loop vs fibers).

## Value bridging: the universal type

U's `UTree` is the interop bridge. It's a tagged union that maps onto both `zval` (Zend) and `v8::Value` (V8) without loss:

```
UTree (U)              zval (PHP 8)            v8::Value (V8/N-API)
─────────────────────  ──────────────────────  ──────────────────────
U_TREE_NONE            IS_NULL / IS_UNDEF      v8::Null / v8::Undefined
U_TREE_BOOL            IS_TRUE / IS_FALSE      v8::Boolean
U_TREE_INT (int64)     IS_LONG (zend_long)     v8::Number (double) / v8::BigInt
U_TREE_NUM (double)    IS_DOUBLE               v8::Number
U_TREE_STR (char*)     IS_STRING (zend_string)  v8::String
U_TREE_BYTES           IS_STRING (binary)       v8::ArrayBuffer / Buffer
U_TREE_LIST            IS_ARRAY (packed)        v8::Array
U_TREE_NODE            IS_ARRAY (assoc)         v8::Object (plain)
U_TREE_TYPED           IS_OBJECT (zend_object)  v8::Object (class instance)
```

### Conversion functions (C level)

```c
// PHP ↔ U
UTree* u_zval_to_tree(zval* zv);           // deep copy zval → UTree
void   u_tree_to_zval(UTree* tree, zval* rv); // deep copy UTree → zval
UTree* u_zval_to_tree_ref(zval* zv);       // shallow — wraps zval pointer in TYPED node

// JS ↔ U (using N-API for ABI stability across Node/Bun versions)
UTree*     u_napi_to_tree(napi_env env, napi_value val);
napi_value u_tree_to_napi(napi_env env, UTree* tree);
```

### Why N-API instead of raw V8

V8's C++ API changes with every Chrome release. Node-API (N-API) is ABI-stable — a module compiled against N-API version 8 works on Node 18, 20, 22, Bun, and Deno without recompilation. Since U compiles to C (not C++), N-API's C interface is also a better fit than V8's C++ API.

**Target: N-API version 9** (stable since Node 18.17, supported by Bun 1.0+).

---

## PHP interop: U ↔ Zend Engine

### Architecture

Two modes of operation:

**Mode A: U hosts Zend (preferred for Safebox)**
U webserver embeds `libphp` (the Zend engine as a shared library). PHP extensions run inside the U process. U calls Zend functions via the C API. The webserver controls the lifecycle.

```
┌─────────────────────────┐
│     U Webserver (34KB)   │
│  ┌───────────────────┐   │
│  │  libphp (Zend)    │   │
│  │  ├─ PDO           │   │
│  │  ├─ curl          │   │
│  │  ├─ openssl       │   │
│  │  └─ pcre          │   │
│  └───────────────────┘   │
│  Transpiled U handlers   │
│  call Zend when needed   │
└─────────────────────────┘
```

**Mode B: Zend hosts U (for existing PHP deployments)**
U compiles to a PHP extension (.so). PHP scripts call U functions via a `u_call()` bridge. Useful for incrementally porting Qbix Platform modules to U.

```
┌─────────────────────────┐
│     PHP-FPM / Qbix WS   │
│  ┌───────────────────┐   │
│  │  u_extension.so   │   │
│  │  (compiled U code) │   │
│  └───────────────────┘   │
│  PHP calls u_call()      │
│  for hot-path functions  │
└─────────────────────────┘
```

### Zend C API surface needed

```c
// ── Lifecycle ────────────────────────────────────────────────
php_embed_init(argc, argv);      // start Zend engine
php_embed_shutdown();             // stop Zend engine
php_request_startup();            // per-request init (superglobals, etc.)
php_request_shutdown();           // per-request teardown

// ── Calling PHP functions from U ─────────────────────────────
// U transpiler emits: PHP.curl_exec(handle) 
// Runtime resolves to: u_php_call("curl_exec", args, nargs, &retval)

int u_php_call(const char* func_name, UTree** args, int nargs, UTree** retval) {
    zval zargs[nargs], zretval;
    for (int i = 0; i < nargs; i++)
        u_tree_to_zval(args[i], &zargs[i]);
    
    zval zfunc;
    ZVAL_STRING(&zfunc, func_name);
    
    int rc = call_user_function(
        CG(function_table),   // global function table
        NULL,                 // no object
        &zfunc,               // function name
        &zretval,             // return value
        nargs,                // arg count
        zargs                 // args array
    );
    
    *retval = u_zval_to_tree(&zretval);
    zval_ptr_dtor(&zretval);
    for (int i = 0; i < nargs; i++) zval_ptr_dtor(&zargs[i]);
    zval_dtor(&zfunc);
    return rc;
}

// ── Calling U functions from PHP ─────────────────────────────
// PHP extension registers: $result = u_call("MyModule.process", $arg1, $arg2);

PHP_FUNCTION(u_call) {
    char* func_name;
    size_t func_len;
    zval* args;
    int argc;
    
    ZEND_PARSE_PARAMETERS_START(1, -1)
        Z_PARAM_STRING(func_name, func_len)
        Z_PARAM_VARIADIC('+', args, argc)
    ZEND_PARSE_PARAMETERS_END();
    
    UTree* uargs[argc];
    for (int i = 0; i < argc; i++)
        uargs[i] = u_zval_to_tree(&args[i]);
    
    UTree* result = u_dispatch(func_name, uargs, argc);  // compiled U function table
    u_tree_to_zval(result, return_value);
}
```

### Handling PHP references (pass-by-reference)

PHP's `&$param` passes a `zval**` (pointer to pointer). U doesn't have references. The bridge:

```c
// When transpiler detects &$param in a PHP function signature:
// 1. Wrap the UTree in a RefCell: { .kind = U_TREE_TYPED, .as.typed.obj = &original }
// 2. After call returns, copy the modified value back

typedef struct {
    UTree* target;  // points to the caller's variable
} URefCell;

// Before call:  wrap caller's UTree* in RefCell
// During call:  Zend modifies the zval in place
// After call:   u_zval_to_tree(modified_zval) → write back to RefCell.target
```

### Superglobals mapping

```
PHP                    U (via Request)              Zend C API
─────────────────────  ─────────────────────────── ──────────────────
$_GET                  req.query                    PG(http_globals)[TRACK_VARS_GET]
$_POST                 req.body                     PG(http_globals)[TRACK_VARS_POST]
$_SERVER               req.server                   PG(http_globals)[TRACK_VARS_SERVER]
$_COOKIE               req.cookies                  PG(http_globals)[TRACK_VARS_COOKIE]
$_FILES                req.files                    PG(http_globals)[TRACK_VARS_FILES]
$_SESSION              req.session                  PS(session_vars)
$_REQUEST              (not mapped — use specific)
$_ENV                  System.env                   PG(http_globals)[TRACK_VARS_ENV]
```

### PHP extensions that MUST use Zend (cannot transpile)

These are C extensions compiled into Zend. The transpiler maps them to `u_php_call()`:

| Extension | Why it can't transpile | Bridge function |
|-----------|----------------------|-----------------|
| **PDO** | Database drivers are C code | `PHP.pdo_query(dsn, sql)` → `u_php_call("pdo_query", ...)` |
| **curl** | libcurl bindings | `PHP.curl_exec(ch)` → `u_php_call("curl_exec", ...)` |
| **openssl** | libssl/libcrypto bindings | `PHP.openssl_encrypt(...)` → `u_php_call(...)` |
| **pcre** | PCRE2 C library | `PHP.preg_match(...)` → `u_php_call(...)` |
| **gd/imagick** | Image processing C libs | `PHP.imagecreate(...)` → `u_php_call(...)` |
| **mbstring** | ICU/oniguruma bindings | `PHP.mb_detect_encoding(...)` → `u_php_call(...)` |
| **sodium** | libsodium bindings | `PHP.sodium_crypto_box(...)` → `u_php_call(...)` |
| **intl** | ICU bindings | `PHP.IntlDateFormatter(...)` → `u_php_call(...)` |
| **zip** | libzip bindings | `PHP.ZipArchive(...)` → `u_php_call(...)` |

### Alternative: native U implementations

Over time, U can replace Zend calls with native implementations that don't need the Zend engine:

| PHP function | U native replacement | Status |
|-------------|---------------------|--------|
| `json_encode/decode` | `json.encode/decode` (already in runtime) | ✅ Done |
| `file_get/put_contents` | `filesystem.read/write` | ✅ Done |
| `strlen/substr/strpos` | `.len`, `.slice`, `.index_of` | ✅ Done |
| `array_*` | `.map`, `.filter`, `.sort`, etc. | ✅ Done |
| `hash/hash_hmac` | `Crypto.hash/hmac` (link libcrypto directly) | 🔜 Link to libcrypto |
| `preg_*` | `Regex.match/replace` (link PCRE2 directly) | 🔜 Link to libpcre2 |
| `curl_*` | `HTTP.fetch` (link libcurl directly) | 🔜 Link to libcurl |
| `PDO` | `Database.*` (already in U runtime) | ✅ Wire protocol built |

The endgame: most PHP code transpiles to pure U. The Zend bridge exists for the long tail of extensions (gd, imagick, intl, specialized extensions) that aren't worth re-implementing.

---

## JavaScript interop: U ↔ V8/N-API

### Architecture

Two modes, same pattern as PHP:

**Mode A: U hosts V8 (preferred)**
U webserver embeds V8 (or links to Bun's JavaScriptCore) and calls JS functions when needed. Transpiled TS handlers run as native U; non-transpilable code runs in the embedded engine.

```
┌─────────────────────────┐
│     U Webserver (34KB)   │
│  ┌───────────────────┐   │
│  │  N-API bridge     │   │
│  │  ├─ V8 / JSC      │   │
│  │  ├─ fetch polyfill │   │
│  │  ├─ crypto.subtle  │   │
│  │  └─ Buffer         │   │
│  └───────────────────┘   │
│  Transpiled U handlers   │
│  call N-API when needed  │
└─────────────────────────┘
```

**Mode B: V8 hosts U (Node/Bun native addon)**
U code compiles to a `.node` addon. JS/TS code calls U functions for hot paths.

```javascript
// JS side:
const u = require('./compiled_module.node');
const result = u.call('ImageProcess.resize', buffer, 300, 200);
```

### N-API C surface needed

```c
// ── Calling JS functions from U ──────────────────────────────
// U transpiler emits: JS.setTimeout(callback, 1000)
// Runtime resolves to: u_napi_call(env, "setTimeout", args, nargs, &retval)

napi_status u_napi_call(napi_env env, const char* func_name,
                         UTree** args, int nargs, UTree** retval) {
    napi_value global, func, nargs_v[nargs], result;
    
    napi_get_global(env, &global);
    napi_get_named_property(env, global, func_name, &func);
    
    for (int i = 0; i < nargs; i++)
        nargs_v[i] = u_tree_to_napi(env, args[i]);
    
    napi_call_function(env, global, func, nargs, nargs_v, &result);
    *retval = u_napi_to_tree(env, result);
    return napi_ok;
}

// ── Calling U functions from JS ──────────────────────────────
// Registered as: exports.process = napi_wrap(u_image_process);

napi_value u_napi_wrapper(napi_env env, napi_callback_info info) {
    size_t argc = 16;
    napi_value argv[16], thisArg;
    napi_get_cb_info(env, info, &argc, argv, &thisArg, NULL);
    
    UTree* uargs[argc];
    for (size_t i = 0; i < argc; i++)
        uargs[i] = u_napi_to_tree(env, argv[i]);
    
    // func_ptr set during napi_define_properties
    UFuncPtr fn = /* retrieved from data pointer */;
    UTree* result = fn(uargs, argc);
    return u_tree_to_napi(env, result);
}
```

### Callbacks: JS → U → JS

When JS passes a callback to U code, U receives it as a `napi_ref` wrapped in a UTree TYPED node. When U needs to invoke it:

```c
typedef struct {
    napi_env env;
    napi_ref func_ref;  // prevent GC from collecting the JS function
} UJsCallback;

UTree* u_invoke_js_callback(UJsCallback* cb, UTree** args, int nargs) {
    napi_value func, nargs_v[nargs], result;
    napi_get_reference_value(cb->env, cb->func_ref, &func);
    
    for (int i = 0; i < nargs; i++)
        nargs_v[i] = u_tree_to_napi(cb->env, args[i]);
    
    napi_value global;
    napi_get_global(cb->env, &global);
    napi_call_function(cb->env, global, func, nargs, nargs_v, &result);
    
    return u_napi_to_tree(cb->env, result);
}
```

### Promises: JS ↔ U async (+A)

This is the critical bridge. JS uses Promises (microtask queue). U uses fibers (+A, cactus stack). They need to interoperate:

**JS Promise → U fiber (+A):**
```c
// When U receives a JS Promise, wrap it as a U fiber that suspends
// until the Promise resolves:

UTree* u_await_js_promise(napi_env env, napi_value promise) {
    // 1. Create a U fiber frame
    UFiberFrame* frame = u_fiber_alloc();
    
    // 2. Register .then() callback that resumes the fiber
    napi_value then_cb;
    napi_create_function(env, "resolve", 7, js_resolve_cb, frame, &then_cb);
    napi_value then_method;
    napi_get_named_property(env, promise, "then", &then_method);
    napi_call_function(env, promise, then_method, 1, &then_cb, NULL);
    
    // 3. Suspend the U fiber — scheduler runs other fibers
    u_fiber_suspend(frame);
    
    // 4. When JS resolves, js_resolve_cb writes result and resumes fiber
    return frame->result;
}

// The resolve callback (called by V8's microtask queue):
napi_value js_resolve_cb(napi_env env, napi_callback_info info) {
    UFiberFrame* frame = /* from data */;
    napi_value argv[1];
    napi_get_cb_info(env, info, &(size_t){1}, argv, NULL, (void**)&frame);
    
    frame->result = u_napi_to_tree(env, argv[0]);
    u_fiber_resume(frame);  // put fiber back on ready queue
    return NULL;
}
```

**U fiber (+A) → JS Promise:**
```c
// When JS calls a U async function, return a JS Promise:

napi_value u_async_to_promise(napi_env env, UFuncPtr async_fn, UTree** args, int nargs) {
    napi_deferred deferred;
    napi_value promise;
    napi_create_promise(env, &deferred, &promise);
    
    // Create fiber that runs the U function, resolves the Promise when done
    UFiberFrame* frame = u_fiber_alloc();
    frame->func = async_fn;
    frame->args = args;
    frame->nargs = nargs;
    frame->on_complete = (UFiberCallback){
        .env = env,
        .deferred = deferred,
        .handler = resolve_promise_cb
    };
    
    u_fiber_enqueue(frame);  // scheduler will run it
    return promise;           // JS gets the Promise immediately
}
```

### JS APIs that MUST use the engine

| API | Why it can't transpile | Bridge |
|-----|----------------------|--------|
| **fetch** | Network stack + TLS + HTTP/2 | `JS.fetch(url)` → `u_napi_call("fetch", ...)` |
| **setTimeout/setInterval** | Event loop integration | `JS.setTimeout(cb, ms)` → `u_napi_call(...)` |
| **crypto.subtle** | WebCrypto C bindings | `JS.crypto.subtle.digest(...)` → `u_napi_call(...)` |
| **Buffer** | V8 ArrayBuffer backing store | `JS.Buffer.from(...)` → `u_napi_call(...)` |
| **fs** (Node) | libuv file operations | `JS.fs.readFile(...)` → `u_napi_call(...)` |
| **EventEmitter** | Node event loop | `JS.events.on(...)` → `u_napi_call(...)` |
| **WebSocket** | Network protocol | `JS.WebSocket(...)` → `u_napi_call(...)` |
| **Worker** | Thread management | `JS.Worker(...)` → `u_napi_call(...)` |

### Native U replacements (same as PHP — reduce bridge calls over time)

| JS API | U native replacement | Status |
|--------|---------------------|--------|
| `JSON.parse/stringify` | `json.decode/encode` | ✅ Done |
| `fs.readFile/writeFile` | `filesystem.read/write` | ✅ Done |
| `String.split/slice/indexOf` | `.split`, `.slice`, `.index_of` | ✅ Done |
| `Array.map/filter/reduce` | `.map`, `.filter`, `.reduce` | ✅ Done |
| `Math.*` | `Math.*` | ✅ Done |
| `crypto.createHash` | `Crypto.hash` (link libcrypto) | 🔜 |
| `fetch` | `HTTP.fetch` (link libcurl or custom) | 🔜 |
| `Buffer` | `[Q8]` (U byte arrays) | ✅ Done |
| `setTimeout` | `Timer.after(ms, fn)` (U scheduler) | 🔜 |

---

## The transpiler's decision tree

When the PHP→U or TS→U transpiler encounters a function call, it follows this priority:

```
1. Is there a U-native equivalent?
   strlen → .len, json_encode → json.encode, Array.map → .map
   → Emit U code. No bridge needed.

2. Is it in the FUNC_MAP with a known U mapping?
   md5 → Crypto.md5, file_get_contents → filesystem.read
   → Emit the U mapping. May need a C library linked.

3. Is it a C library function U can link directly?
   preg_match → u_pcre2_match (link libpcre2)
   curl_exec → u_curl_exec (link libcurl)
   → Emit U wrapper. Link the C library at compile time.

4. Is it a runtime-specific function?
   PDO::query, crypto.subtle.digest, EventEmitter
   → Emit bridge call: u_php_call() or u_napi_call()
   → Requires Zend or V8 to be embedded/linked.

5. Is it completely unknown?
   → Emit: // INTEROP: unknown function "foo_bar" — needs manual bridge
   → Compilation continues; runtime will error if called.
```

---

## Lifecycle management

### PHP: request isolation

Zend expects request boundaries (startup/shutdown). The U webserver calls:
```c
// Per-request lifecycle (same as php-fpm):
php_request_startup();
// ... serve the request (transpiled U code + Zend bridge calls) ...
php_request_shutdown();  // cleans up zvals, closes resources, resets statics
```

This solves the "mutable global state" problem. Zend's own cleanup handles what U's immutability handles for transpiled code. Extensions that leak state between requests are caught by Zend's shutdown, not by U.

### JS: GC coordination

V8/JSC has a GC. U has ARC. When a JS object is wrapped in a UTree:
- The `napi_ref` prevents V8 from collecting the JS object
- When U drops the UTree (ARC reaches zero), it calls `napi_delete_reference`
- The JS object becomes eligible for GC

When a U object is passed to JS:
- N-API weak reference + platform destructor
- V8 GC calls the destructor → decrements U ARC count

### Event loop integration

PHP has no event loop (synchronous). JS runs on an event loop (libuv/V8 microtasks).

For Mode A (U hosts the engine):
- U's fiber scheduler IS the event loop
- JS microtasks run between fiber switches (via `napi_run_environment` or `uv_run(loop, UV_RUN_NOWAIT)`)
- Timer callbacks from JS → enqueue U fiber

For Mode B (engine hosts U):
- U fibers run inside the host's event loop
- `u_fiber_tick()` called from the host's idle handler
- U async (+A) functions integrate with the host's Promise system

---

## Build configuration

```makefile
# Minimal: pure U, no interop (Safebox default)
u2c handler.u -o handler.so

# With PHP interop:
u2c handler.u -lphp8-embed -I/usr/include/php -o handler.so

# With JS interop (N-API):
u2c handler.u -lnode -I/usr/include/node -o handler.node

# With both:
u2c handler.u -lphp8-embed -lnode -o handler.so

# With native C library replacements (no engine needed):
u2c handler.u -lpcre2-8 -lcurl -lcrypto -o handler.so
```

---

## Migration path for Qbix Platform

The Qbix Platform is ~470 PHP files. The migration is incremental:

**Phase 1: Transpile pure-logic modules**
Modules that don't use C extensions: config loading, event dispatch, access control, stream operations, tree merge/diff. These transpile to pure U with no Zend bridge.

**Phase 2: Bridge C-extension calls**
Modules that use PDO, curl, openssl → transpile the PHP logic, bridge the C calls via `u_php_call()`. The Zend engine is embedded, but only used for extension functions.

**Phase 3: Replace bridges with native U**
Replace `u_php_call("curl_exec", ...)` with `HTTP.fetch(...)` (native U, links libcurl directly). Replace `u_php_call("preg_match", ...)` with `Regex.match(...)` (native U, links libpcre2). Each replacement eliminates one reason to embed Zend.

**Phase 4: Zend-free**
When all bridges are replaced with native implementations, the Zend engine dependency drops. The webserver is back to a single binary + linked C libraries. No PHP runtime.

---

## Summary

| Axis | PHP (Zend) | TypeScript (V8/N-API) |
|------|-----------|----------------------|
| **Value bridge** | `zval ↔ UTree` | `napi_value ↔ UTree` |
| **Call U→Engine** | `u_php_call(name, args)` | `u_napi_call(env, name, args)` |
| **Call Engine→U** | `u_call()` PHP function | N-API registered functions |
| **Async model** | Zend is synchronous; U fibers handle async | JS Promises ↔ U fibers (+A) |
| **GC bridge** | Zend refcount ↔ U ARC | V8 GC weak refs ↔ U ARC destructor |
| **Reference passing** | RefCell wrapper for `&$param` | N/A (JS passes by sharing) |
| **Embedding** | `php_embed_init()` / libphp.so | N-API / link V8 or JSC |
| **Target API version** | PHP 8.1+ (Zend Engine 4) | N-API version 9 (Node 18+, Bun 1.0+) |
| **Endgame** | Most calls native; Zend only for exotic extensions | Most calls native; V8 only for platform APIs |

---

## Test Results (September 2026)

### PHP Bridge — zval ↔ UTree (14/14 ✅)

Tested with mock zval matching Zend Engine 4 (PHP 8.x) memory layout:

| Test | Status |
|------|--------|
| null roundtrip | ✅ |
| bool true/false roundtrip | ✅ |
| int 42, int 2^53, int negative | ✅ |
| double 3.14159 roundtrip | ✅ |
| string, empty string, unicode (héllo 世界 🌍) | ✅ |
| packed array [1, "two", 3.0] | ✅ |
| assoc array {host: localhost, port: 8080, debug: true} | ✅ |
| nested: {users: [{name, age}, {name, age}]} — deep roundtrip | ✅ |
| fiber scheduler: enqueue/dequeue/suspend/resume/complete | ✅ |

### PHP Transpiler — Advanced Patterns (17/17 ✅)

| Pattern | Category | Status |
|---------|----------|--------|
| `match($code) { 200 => 'OK' }` | PHP 8.0 | ✅ |
| `createUser(name: 'Greg', age: 35)` | PHP 8.0 named args | ✅ |
| `fn($x) => $x * 2` | PHP 7.4 arrow functions | ✅ |
| `$user?->getAddress()?->getCountry()` | PHP 8.0 null-safe | ✅ |
| `[...$base, 4, 5]` | PHP 7.4 spread | ✅ |
| `function process(Countable $c)` | Type hints | ✅ |
| `new Fiber(function() { ... })` | PHP 8.1 fibers | ✅ |
| Q event handler with Streams/compact() | Qbix Platform | ✅ |
| Access control (fetch/testReadLevel) | Qbix Platform | ✅ |
| Static method chaining `User::where()->orderBy()` | ORM pattern | ✅ |
| Middleware (handle + $next) | Laravel | ✅ |
| Controller with DI | Laravel | ✅ |
| Eloquent-style query builder | Laravel | ✅ |
| Service with typed constructor + defaults | Symfony | ✅ |
| try/catch/finally | Error handling | ✅ |
| foreach with key => value | Iteration | ✅ |
| Class inheritance (extends) | OOP | ✅ |

### N-API Bridge — napi_value ↔ UTree (36/36 ✅)

Real Node.js v22 native addon compiled and loaded:

| Category | Tests | Status |
|----------|-------|--------|
| Type detection (null, undefined, bool, int, float, string, array, object, Buffer) | 10 | ✅ |
| Value roundtrip (all types including nested objects, unicode, Buffer) | 15 | ✅ |
| Calling U functions from JS (Math.add, multiply, String.upper, Array.sum) | 5 | ✅ |
| Fiber scheduler simulation (suspend/resume across "promise boundary") | 1 | ✅ |
| Promise bridge: async compute → resolve with value | 1 | ✅ |
| Promise bridge: resolve with complex object | 1 | ✅ |
| Promise.all (3 parallel computations) | 1 | ✅ |
| Promise.race (fast computation wins) | 1 | ✅ |

### TypeScript/JS Patterns via Bridge (22/22 ✅)

| Category | Tests | Status |
|----------|-------|--------|
| Interface-shaped objects (User, Result\<T\>, Record\<K,V\>) | 5 | ✅ |
| Class patterns (constructor, inheritance) | 2 | ✅ |
| Async: simple value, Promise\<User\>, Promise\<Item[]\> | 3 | ✅ |
| Promise.all with typed destructuring | 1 | ✅ |
| Chained async pipeline (3 stages) | 1 | ✅ |
| U function calls from JS (sum, upper) | 2 | ✅ |
| Edge cases (empty, deep nesting, 1000 elements, mixed types, special chars, MAX_SAFE_INTEGER) | 8 | ✅ |

### Grand Total: 123/123 ✅

| Suite | Tests | Status |
|-------|-------|--------|
| PHP Bridge (mock zval) | 14/14 | ✅ |
| PHP Transpiler (advanced) | 17/17 | ✅ |
| **Real Zend Engine** (PHP 8.3.6) | **24/24** | ✅ |
| N-API Bridge (Node v22) | 36/36 | ✅ |
| TS/JS Patterns via Bridge | 22/22 | ✅ |
| **Bidirectional Event Loop** (libuv ↔ fibers) | **10/10** | ✅ |

---


### Bidirectional Event Loop — libuv ↔ U Fibers (10/10 ✅)

The "most complex part" from the original plan, now tested:

| Test | Direction | Status |
|------|-----------|--------|
| libuv timer (30ms) → U fiber wake → Promise resolve | libuv → U | ✅ |
| 3 parallel timer fibers | libuv → U (concurrent) | ✅ |
| Worker thread computation → fiber resume → Promise | thread → U → JS | ✅ |
| 4 parallel worker fibers | thread → U → JS (concurrent) | ✅ |
| JS setTimeout → trigger() → fiber wake → Promise | JS → U → JS | ✅ |
| JS event with complex object payload → fiber | JS → U (complex data) | ✅ |
| timer + worker + JS event all interleaved | all three simultaneously | ✅ |
| Fiber stats tracking | bookkeeping | ✅ |
| Event log tracing (fiber_created→timer_fired→fiber_completed) | observability | ✅ |

The key test: **"timer + worker + JS event all interleaved"** — a libuv timer, a pthread worker, and a JS setTimeout all fire at different times, each waking a different U fiber, all three resolving their JS Promises correctly. This proves the event loop bridge works bidirectionally under concurrent load.

Implementation: `u_event_loop.h` (332 lines) + `u_event_loop_addon.c` (291 lines).

## What still doesn't work

### PHP interop — TESTED against real Zend Engine 4.3.6 (PHP 8.3.6)

**24/24 tests pass** with real `libphp8.3-embed`:

| Test | Status |
|------|--------|
| zval LONG/DOUBLE/STRING/BOOL/NULL ↔ UTree roundtrip | ✅ (6 tests) |
| Packed array [1, "two", 3.0] roundtrip via real HashTable | ✅ |
| Associative array {host, port, debug} roundtrip via real HashTable | ✅ |
| Nested {users: [{name, age}]} via real nested zvals | ✅ |
| `strlen("hello")` via `call_user_function()` bridge | ✅ returns INT 5 |
| `strtoupper("hello world")` via bridge | ✅ returns "HELLO WORLD" |
| `array_sum([1,2,3,4,5])` via bridge | ✅ returns INT 15 |
| `json_encode({name, age})` via bridge | ✅ returns valid JSON string |
| `json_decode('{"x":42}', true)` via bridge | ✅ returns UTree NODE |
| `str_replace("world", "U", "hello world")` via bridge | ✅ returns "hello U" |
| `md5("hello")` via bridge | ✅ returns 5d41402abc4b2a76b9719d911017c592 |
| `base64_encode/decode` roundtrip via bridge | ✅ |
| `preg_match('/\d+/', 'abc 123 def')` via bridge (PCRE2) | ✅ returns 1 |
| `date("Y")` via bridge | ✅ returns "2026" |
| `zend_eval_string("42 + 8")` → 50 | ✅ |
| Define + call PHP function via eval + bridge | ✅ |
| Define PHP class with method chaining via eval | ✅ `->add(10)->add(20)->add(12)->result()` = 42 |
| Sort array via `zend_eval_string` + `json_encode` | ✅ [1,1,2,3,4,5,6,9] |

**What still needs work (Zend-specific):**

| Feature | Status | Notes |
|---------|--------|-------|
| **`call_user_function()` with all types** | ✅ Working | Tested with 11 PHP built-in functions |
| **PHP class from zend_eval_string** | ✅ Working | Method chaining, property access work |
| **Pass-by-reference (`sort(&$arr)`)** | ⚠️ Warning | `sort()` warns — need RefCell wrapper for `&$param` |
| **PHP generators/yield** | 🔜 Not tested | Generator → fiber frame mapping needed |
| **PHP output buffering (echo)** | 🔜 Not tested | Redirect Zend output buffer to U Response |
| **PHP object ↔ UTree TYPED** | 🔜 Partial | Class name preserved via `__class` key; full property bridge works |
| **PHP session handling** | 🔜 Not tested | Needs request lifecycle integration |
| **Zend error → U exception** | 🔜 Not tested | `zend_try/zend_catch` works for fatal; need `set_error_handler` |

### JavaScript/TypeScript interop — not yet tested

| Feature | Why it matters | What's needed |
|---------|---------------|---------------|
| **JS callback → U closure** | `array.forEach(callback)` where callback is U code | `napi_ref` wrapping of JS function + trampoline |
| **U closure → JS callback** | Pass a U function where JS expects `(err, result) =>` | N-API function wrapping with captured U function pointer |
| **EventEmitter integration** | Node.js event loop ↔ U fiber scheduler | `uv_poll` / `uv_idle` hooks into libuv |
| **Stream/pipe integration** | Node Readable/Writable ↔ U IO streams | Stream adapter bridging backpressure |
| **Worker threads** | U computations on Node worker threads | `napi_create_threadsafe_function` per worker |
| **BigInt** | JS BigInt ↔ U arbitrary-precision int | `napi_get_value_bigint_int64` / words API |
| **Symbol / WeakRef / FinalizationRegistry** | GC coordination for shared objects | Custom weak reference bridge |
| **TypeScript decorators at runtime** | `@Injectable()`, `@Route('/users')` | Decorator metadata → U +D modifier at compile time (not runtime) |
| **ES modules (import/export)** | Module resolution in embedded V8 | Module loader hooks in N-API |

### Transpiler gaps (both PHP and TS)

| Pattern | Language | Issue | Workaround |
|---------|----------|-------|------------|
| **PHP traits** | PHP | No U equivalent | Transpile trait methods inline into each using class |
| **PHP `global $var`** | PHP | U has no globals | Pass as parameter or use module-level constant |
| **PHP `extract()`/`compact()`** | PHP | Dynamic variable creation | Transpile to explicit assignments |
| **PHP variable variables (`$$var`)** | PHP | No U equivalent | Use a map: `vars[name]` |
| **TS conditional types** | TS | `T extends U ? X : Y` — no U equivalent | Erase to Tree (type-level only, no runtime effect) |
| **TS mapped types** | TS | `Partial<T>`, `Required<T>` | Erase to base type |
| **TS template literal types** | TS | Type-level string manipulation | Erase |
| **TS declaration merging** | TS | Interface + namespace merge | Flatten at transpile time |
| **TS `satisfies` operator** | TS | Compile-time only | Erase |

### Architecture gaps

| Gap | Impact | Effort |
|-----|--------|--------|
| **No embedded Zend in the binary** | Can't call PHP extensions | ~500 lines: embed libphp, init/shutdown per request |
| **No embedded V8/JSC in the binary** | Can't call JS runtime APIs | Use N-API addon mode (tested ✅) or embed via libnode |
| ~~No libuv integration~~ | ✅ **DONE.** libuv ↔ U fiber bidirectional bridge: `uv_idle` runs fibers between IO events, `napi_threadsafe_function` bridges worker threads, JS events wake U fibers via trigger functions. 332 lines in `u_event_loop.h`. | ✅ Tested |
| **No WASM bridge** | Can't call U functions from browser JS directly | Need wasm-bindgen or manual WASM export table |
| **Transpiler not baked into webserver** | PHP→U transpilation still needs external tool | ~200 lines: JS→U transpile the JS transpiler, compile into binary |
