plgg-fetch
A typed HTTP client, built from scratch on plgg, for the presentation layer to call a server. It is the symmetric peer of plgg-server: both build on the shared plgg-http model — so neither imports the other — requests/responses are pure plgg data, failures are values, and native fetch/Request/Response appear only at one seam. Runtime deps: plgg and plgg-http.
Usage
import { get, post, decodeJsonBody } from "plgg-fetch";
import {
pipe, matchResult, cast,
asObj, asSoftStr, forProp, encodeJson,
} from "plgg";
const result = await get("https://api.example.com/users/7");
pipe(
result,
matchResult(
(err) => console.error("transport failed:", err), // NetworkError only
(res) =>
pipe(
res,
decodeJsonBody((v) =>
cast(v, asObj, forProp("name", (x) => cast(x, asSoftStr))),
),
matchResult(
() => console.log(`status ${res.status.content}, unexpected shape`),
(user) => console.log("user:", user),
),
),
),
);API
request(method, url, { headers?, query?, body? })— the core call; all arguments at once (not apipestep).get/post/put/patch/del— method conveniences overrequest(del, sincedeleteis reserved).decodeJsonBody(as)(response)— read the text body,decodeJson, then run acast-based parser; the chain is aResult<T, InvalidError>.
All calls return PromisedResult<HttpResponse, ClientError>, where ClientError = HttpError | NetworkError — the shared HttpError vocabulary widened with the client-only NetworkError.
Failure policy
This is the central contract:
- A non-2xx status is a valid response, not an error.
404,500, etc. come back asOk(HttpResponse); the caller inspectsresponse.statusand decides. The client never guesses what a status "means" for your domain. - Only a transport/build failure is an error. A DNS failure, refused connection, malformed URL, or unreadable body folds to
Err(NetworkError)— there is no HTTP response in that case.
Note the consumption idiom: the client folds responses with matchResult, not proc — per the house style, proc is for server/async orchestration, while the client matches the result explicitly.
The seam
Web platform types live in exactly one module: toFetchRequest(req): Request (plgg → native) and fromFetchResponse(res): PromisedResult<HttpResponse, ClientError> (native → plgg). This mirrors the server's seam in the opposite direction, reusing the same plgg-http model so client and server stay symmetric.
Out of scope for the POC: retries, interceptors, streaming response bodies, auth/cookie flows, and cancellation.