Networking
PocketJS provides a small, bounded HTTP client through the NET module. It is fetch-shaped without importing the browser's complete networking stack.
import { fetch } from "@pocketjs/framework/net";
const response = await fetch("https://api.example.com/items", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ name: "Pocket" }),
timeoutMs: 5_000,
maxBytes: 64 * 1024,
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();The public surface includes common application methods, string and byte
request bodies, headers, a timeout, a response-size budget, and buffered
text(), json(), bytes() and arrayBuffer() reads. It deliberately omits
streams, cookies, cache, Request, Headers, AbortSignal, WebSocket,
servers, and raw sockets.
Why responses are buffered
The first version resolves fetch only after the body is complete. The
native transport still reads chunks and stops as soon as maxBytes is
exceeded; the transport-neutral core checks the final size again. This keeps
the JS API and every embedded adapter small without allowing an unbounded
response into memory.
The default body budget is 128 KiB and the absolute maximum is 256 KiB. A request body is limited to 64 KiB. Media downloads and other payloads that fundamentally require streaming are not NET v1 use cases.
Tick delivery and polling
Network work may happen on native threads, but those threads never call the guest. The host drains completions at a tick boundary, then the framework settles fetch Promises in the guest's normal turn.
There is no idle native poll. The first pending fetch registers a small
framework-neutral service pump and the final completion removes it. While
requests are pending the SDK calls net.poll() once per guest tick; that one
call returns the entire visible completion batch.
What belongs where
| Layer | Artifact | Responsibility |
|---|---|---|
| SDK | framework/src/net-api.ts |
fetch-shaped guest API and Promise delivery |
| Spec | contracts/spec/net.ts |
ops, events, limits, errors, ownership and tick contract |
| Core | engine/crates/pocket-net |
handles, validation, bodies and a transport interface |
| Sim | hosts/sim/net.ts |
deterministic fixture routes |
| Browser host | hosts/web/net.js |
bounded adapter over browser fetch |
| Host adapter | the owning runtime | DNS, TLS, HTTP client library, workers and credentials |
PocketJS does not force one HTTP library on every platform. A desktop host can
adapt ureq, an ESP host can adapt esp_http_client, and an Apple host can
adapt URLSession. A product-specific runtime keeps that adapter in its own
repository. Only adapters for hosts owned and tested by PocketJS belong under
this repository's hosts/ directory.
The Rust boundary is deliberately only start, cancel, and non-blocking
drain. The reference core supplies every portable rule around it, so
changing an HTTP library cannot change what guest code observes.
Limits
| Resource | V1 limit |
|---|---|
| Concurrent requests | 2 |
| Request body | 64 KiB |
| Response body | 128 KiB default, 256 KiB maximum |
| Headers | 32 fields / 8 KiB |
| Timeout | 30 s default, 120 s maximum |
| Redirects | 3 |
Supported methods are GET, HEAD, POST, PUT, PATCH, DELETE, and
OPTIONS. CONNECT and TRACE have tunnel, proxy, and security semantics
that do not belong in an application fetch primitive. A closed method set also
means every host can make the same guarantee.
Transport failures reject with NetError and a portable code such as
dns, connect, tls, timeout, or response_too_large. HTTP status codes
do not reject: a 404 response resolves with ok === false, like browser
fetch.