Saltar al contenido

API web

Además de ECMAScript, Phasis incluye varios paquetes escalonados de API WHATWG / W3C:

  • Paquete de plataforma web — tipos de valor puros, sin E/S: URL, URLSearchParams, URLPattern, TextEncoder, TextDecoder, atob, btoa, structuredClone, performance, DOMException.
  • Paquete Fetch — HTTP real de JavaScript: fetch, Request, Response, Headers, Body, AbortController, AbortSignal, Blob, File, FormData, EventTarget, Event, Streams completos de WHATWG (ReadableStream, WritableStream, TransformStream, estrategias de encolado, BYOB), CompressionStream / DecompressionStream (gzip / deflate / deflate-raw mediante zlib; brotli cuando se carga la extensión PECL kjdev/php-ext-brotli), navigator y el alias global self de la especificación HTML + reportError.
  • Crypto — crypto.getRandomValues, crypto.randomUUID y SubtleCrypto completo: digest (SHA-1/256/384/512), HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA (P-256/384/521), ECDH, HKDF y PBKDF2, además de importación y exportación JWK. Funciona con ext-openssl.
  • WebSocket — constructor, códec de tramas RFC 6455 y eventos. El transporte predeterminado usa stream_socket_client; puede sustituirse mediante Engine::setWebSocketTransport() por ReactPHP / Ratchet / simulaciones en pruebas.
  • XMLHttpRequest — implementado sobre el transporte fetch, por lo que se aplican los mismos hooks de política y el transporte intercambiable.
  • Bucle de eventos — setTimeout, setInterval, clearTimeout, clearInterval, queueMicrotask. Además, AsyncContext (Stage 3) propaga valores entre los límites de microtareas y temporizadores.

Todos los paquetes están activados de forma predeterminada. Sin indicadores ni configuración: typeof fetch devuelve "function" en un new Engine() nuevo.

Conformidad

El corpus importado de Web Platform Tests (tests/Wpt/fixtures/) abarca AbortController / AbortSignal, atob / btoa, Blob / File, TextEncoder / TextDecoder, EventTarget / Event, FormData, Headers, performance.now, todo el estándar Streams (Readable/Writable/Transform + BYOB + encolado), structuredClone, URL / URLSearchParams, toda la superficie de crypto / SubtleCrypto (digest, HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA, ECDH, HKDF, PBKDF2, incluidos miles de casos de permutación de derive-bits de hkdf y pbkdf2), la máquina de estados de WebSocket (constructor URL y validación de subprotocolos, semántica de códigos de cierre, send() para cadenas / ArrayBuffer / vistas de arrays tipados / Blob, binaryType, bufferedAmount, cierre asíncrono durante la conexión, posibilidad de asignar null a los manejadores de eventos) y una amplia parte de Fetch + XMLHttpRequest: máquina de estados de responseType, transiciones de cancelación y envío, encabezados permitidos por CORS, cargas multipart de FormData, XMLHttpRequestUpload + ProgressEvent, validación de inicialización de Response/Headers, constructores estáticos Response.json / Response.error, eliminación de Authorization en cadenas de redirección, URL de blobs URL.createObjectURL, consumo del mixin del cuerpo (text / blob / json / formData / arrayBuffer), streams divididos con structuredClone en Response.clone y errores de red en XHR síncrono. Se aprueban todas las subpruebas importadas.

Para ampliar la cobertura, añade un nuevo *.any.js a tests/Wpt/fixtures/<category>/: el ejecutor lo detecta automáticamente. Las suites XHR/Fetch se ejecutan con tests/Wpt/fetch-server.php (un emulador en un solo archivo de los controladores resources/*.py de WPT); la suite WebSocket usa un transporte inerte dentro del ejecutor que simula apertura, eco y cierre, suficiente para los casos de prueba importados. Ejecuta el corpus tú mismo:

Shell
bin/wpt                # everything
bin/wpt --category headers --verbose   # one area, per-subtest output
bin/wpt --json         # machine-readable

Ejemplo de fetch()

JavaScript
const res = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "alice" }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();

Funciona directamente. El transporte predeterminado es PHP ext-curl. Los cuerpos se transmiten mediante ReadableStream de WHATWG, que incluye Phasis, por lo que for await (const chunk of res.body) también funciona.

Ejemplo de AbortController

JavaScript
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5000);
try {
  const res = await fetch(slowUrl, { signal: ctrl.signal });
} catch (e) {
  if (e.name === "AbortError") {
    // request was cancelled
  }
}

AbortSignal.timeout(ms) también está implementado (con un plazo límite diferido: comprueba la lectura aborted, en vez de emitir un evento a mitad del bucle de eventos; funciona con transportes fetch que consultan la señal durante la transferencia).

Controles del entorno anfitrión (lado de PHP)

Hay tres hooks en la clase Phasis\Engine que controlan el comportamiento de fetch:

PHP
$engine = new Phasis\Engine();

// 1. Swap the HTTP transport. The callable receives a request descriptor
//    and an optional AbortSignal; returns a response descriptor.
$engine->setFetchTransport(function (array $request, $signal) {
    // ... use Guzzle, Symfony HttpClient, your stub, etc.
    return [
        'status' => 200,
        'statusText' => 'OK',
        'headers' => [['content-type', 'application/json']],
        'body' => '{"data":"value"}',
    ];
});

// 2. Pre-flight policy hook — rewrite, deny, or allow.
$engine->setFetchPolicy(function ($request) {
    $url = $request->get('url')->value;
    if (!str_starts_with($url, 'https://')) {
        throw new \RuntimeException('HTTPS only');
    }
    return null;  // allow as-is; return modified Request to rewrite
});

// 3. Cookie jar. Opt-in. Accepts any object with get(url):string and
//    set(url, header):void methods. JsObject or PHP object both work.
$engine->setCookieJar($myJar);

Si no se llama a setFetchTransport(), se ejecuta el CurlTransport predeterminado. El almacén de cookies es optativo: no hay uno predeterminado y no se guarda ninguna cookie a menos que proporciones uno.

Ejemplo de Streams

JavaScript
// Producer: ReadableStream that yields numbers 1..5
const rs = new ReadableStream({
  start(controller) {
    for (let i = 1; i <= 5; i++) controller.enqueue(i);
    controller.close();
  },
});

// Consumer: async iteration
for await (const value of rs) {
  console.log(value);  // 1, 2, 3, 4, 5
}

// Pipe through a transform
const ts = new TransformStream({
  transform(chunk, controller) { controller.enqueue(chunk * 2); },
});
const doubled = new ReadableStream({
  start(c) { [1, 2, 3].forEach(n => c.enqueue(n)); c.close(); }
}).pipeThrough(ts);
for await (const v of doubled) console.log(v);  // 2, 4, 6

Especificación completa de Streams: ReadableStream, WritableStream, TransformStream, ReadableStreamDefaultController, ReadableByteStreamController, ReadableStreamDefaultReader, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, WritableStreamDefaultController, WritableStreamDefaultWriter, TransformStreamDefaultController, ByteLengthQueuingStrategy, CountQueuingStrategy, además de pipeTo / pipeThrough / tee.

Bucle de eventos

setTimeout, setInterval, clearTimeout, clearInterval y queueMicrotask están en el objeto global. Las microtareas (manejadores de Promise, queueMicrotask) se vacían por completo entre cada macrotarea; los temporizadores se ejecutan según el orden de vencimiento mediante una cola por entorno.

JavaScript
console.log('sync');
queueMicrotask(() => console.log('microtask'));
setTimeout(() => console.log('timer'), 0);
// → sync, microtask, timer

const id = setInterval(() => console.log('tick'), 1000);
// ...later
clearInterval(id);

await new Promise(r => setTimeout(r, 50));   // common "delay" pattern works

Controlado por Engine::runEventLoop() de Phasis, que eval() y evalAsModule() invocan automáticamente cuando termina el código síncrono. El bucle usa tiempo virtual de forma predeterminada: setTimeout(cb, 5000) no duerme PHP; el bucle adelanta el reloj hasta el plazo para que el programa termine rápidamente. Los integradores en tiempo real ejecutan tickEventLoop() por su cuenta. Consulta API: bucle de eventos para conocer la superficie del entorno anfitrión.

Lo que no se incluye

Estas API, exclusivas del navegador o propias del modelo de ejecución, quedan deliberadamente fuera del alcance; consulta Limitaciones conocidas para ver todos los detalles:

  • window, document y el árbol DOM
  • localStorage, sessionStorage, IndexedDB
  • Worker, SharedWorker, MessageChannel
  • Service Workers, Push API, Notifications, BroadcastChannel

Si hace falta, el integrador puede implementar la mayoría con $engine->setGlobal().

Ampliar el corpus WPT

tests/Wpt/upstream/ es una extracción parcial de web-platform-tests/wpt que cubre fetch/, streams/, FileAPI/, xhr/formdata/, dom/abort/ y dom/events/. Para ampliar la cobertura, copia cualquier archivo *.any.js de upstream/<area>/ a tests/Wpt/fixtures/<category>/. El ejecutor lo detectará la próxima vez que se ejecute; los fallos se registran mediante bin/wpt --verbose.