Node.js
Adapter
Node request primitives bridged into the server helpers.
- Package
- @queryweave/node
- Runtime
- Node.js
- Depends on
- @queryweave/core, @queryweave/server
- Framework required
- No
What it owns
Section titled “What it owns”One thing: turning a Node IncomingMessage into an absolute URL. Everything after that is
delegated to @queryweave/server, so decoding lives in exactly one place.
What it does not own
Section titled “What it does not own”Decoding, validation, defaults, navigation, and any HTTP framework. Express, Fastify, NestJS, and Hono are not dependencies and never will be — they all expose a request object this package can read.
Request-scoped source — reads once, never navigates
Request-scoped source — reads once, never navigates
- IncomingMessage (incoming)
- QuerySource (read-only)
- QueryModel.decode() (typed result)
IncomingMessage → QuerySource. QuerySource → QueryModel.decode().
Install
Section titled “Install”pnpm add @queryweave/core @queryweave/server @queryweave/nodeExample
Section titled “Example”import { createServer } from "node:http";
import { readNodeQuery } from "@queryweave/node";
createServer((request, response) => { const result = readNodeQuery(request, products); const values = result.ok ? result.value : { ...products.defaults(), ...result.partial };
response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ values, issues: result.issues }));}).listen(3000);Resolving the URL
Section titled “Resolving the URL”A Node request carries a path, not an absolute URL, so the authority has to come from somewhere:
import { resolveNodeRequestUrl } from "@queryweave/node";
resolveNodeRequestUrl(request);// http://<host header>/products?page=2
resolveNodeRequestUrl(request, { host: "example.com", protocol: "https" });// https://example.com/products?page=2The order of precedence is: explicit options, then forwarded headers if you opted in, then the
host header, then the fallback host queryweave.invalid.
A request-scoped source
Section titled “A request-scoped source”import { createNodeQuerySource } from "@queryweave/node";
const source = createNodeQuerySource(request);source.read(); // "?page=2"source.request; // the original IncomingMessageAsynchronous validation
Section titled “Asynchronous validation”const result = await readNodeQueryAsync(request, products);
const values = result.ok ? result.value : { ...products.defaults(), ...result.partial };const rows = await searchProducts(values);result.issues; // keep normalized validation issues for loggingSame rule as everywhere else: use the asynchronous form when a parameter or the model uses an asynchronous refinement.
Edge cases
Section titled “Edge cases”- A request with no URL resolves to
/. - A comma-separated forwarded header uses the first entry, trimmed.
- An empty header value is ignored rather than producing an empty host.
- The fallback host is
queryweave.invalid, chosen because.invalidcan never resolve.
How it is tested
Section titled “How it is tested”tests/node/node.test.ts covers URL resolution, header precedence, the forwarded-header opt-in,
and decoding. The node consumer fixture runs the packed archive against a real
node:http server.