import { Port, VennError } from "@venn-lang/contracts"; import { ActionDefinition, PluginDefinition } from "@venn-lang/sdk"; //#region src/port/http-client.types.d.ts /** One request, ready to send: the verb, the absolute URL and the payload. */ interface HttpRequest { method: string; url: string; headers?: Record; body?: string; /** Aborted when a `race` this request runs inside has already been won. */ signal?: AbortSignal; } /** What an http verb hands back, and what `res` holds in a flow. */ interface HttpResponse { status: number; ok: boolean; headers: Record; body: string; json: unknown; /** * Whole milliseconds the round trip took, from the request going out to the * body being in hand. * * Every implementation measures it, including the double, and none of them * takes it from a canned response: a number a test wrote by hand would make * `expect res.time < 2s` pass for a reason that has nothing to do with time. */ time: number; } /** * The three ways a request fails that every implementation names alike: * nothing accepted the connection, the name did not resolve, and no answer * came back in time. * * They are told apart rather than folded into one message because the three * ask for three different things of whoever reads them: start the service, * fix the address, or wait longer. */ type HttpFailure = "refused" | "not-found" | "timeout"; /** * Sending one request and reading the reply. * * Two implementations: `createFetchClient` over a real socket and * `createFakeClient` for tests. The conformance suite is what says they agree. * * A failure is a `VennError` carrying a `VN7xxx` code, never the host * runtime's own words. See {@link HttpFailure}. */ interface HttpClient { request(req: HttpRequest): Promise; } //#endregion //#region src/port/http-client.port.d.ts /** * The port descriptor an http verb resolves through `ctx.port(...)`. * * Declares the `net` capability, so a host that cannot open sockets refuses the * binding at load time with a readable diagnostic. */ declare const HttpClientPort: Port; //#endregion //#region src/clients/fake-client.d.ts /** * A 200 response with a small JSON body, for tests that only care about a field * or two. * * `time` is not one of the fields worth setting: the double stamps what the * call really took over whatever a canned response carries. * * @param overrides Fields to replace on the default response. * @returns A complete {@link HttpResponse}. */ declare function okResponse(overrides?: Partial): HttpResponse; /** * The double: answers from a table keyed by the request's full URL. * * A URL with no entry gets {@link okResponse}, so a test only has to name the * responses it cares about. Never touches the network. * * It fails the way the network fails, and takes the time it says it takes: a * double that always answered instantly and always answered something would be * no use to a test about a service that is slow or down. * * @param args.responses Canned responses, keyed by the URL the flow requests. * @param args.failures URLs that fail instead of answering, and how. * @param args.latency Milliseconds to really wait before answering, which is * then what `res.time` reports. * @returns An {@link HttpClient} that stays offline. */ declare function createFakeClient(args?: { responses?: Record; failures?: Record; latency?: number; }): HttpClient; //#endregion //#region src/clients/fetch-client.d.ts /** * The real client, over the global `fetch`. Requires the `net` capability. * * The body is always read as text and parsed afterwards, so a reply that claims * JSON but is not still arrives whole in `body` instead of throwing. * * @returns An {@link HttpClient} that sends over the network. */ declare function createFetchClient(): HttpClient; //#endregion //#region src/clients/http-client.errors.d.ts /** One attempt, as a failure names it: `GET https://api.test/health`. */ interface Attempt { method: string; url: string; elapsedMs: number; } /** * The failure for one of the three the port promises, whoever raised it. * * Both implementations come through here, so the double refuses a connection in * exactly the words the real client uses. * * @param args.attempt The request that failed, and how long it had been running. * @param args.failure Which of the three it was. * @returns The `VennError` to throw: `VN7022`, `VN7023` or `VN7024`. */ declare function requestFailed(args: { attempt: Attempt; failure: HttpFailure; }): VennError; /** VN7022: the address was reachable and nothing there accepted the connection. */ declare function connectionRefused(attempt: Attempt): VennError; /** VN7022: a port no HTTP client will open, such as 1 or 25, so nothing was tried. */ declare function portNotAllowed(attempt: Attempt): VennError; /** VN7023: the name did not resolve, so there was nowhere to send it. */ declare function hostNotFound(attempt: Attempt): VennError; /** VN7024: it went out and nothing came back before the time ran out. */ declare function requestTimedOut(attempt: Attempt): VennError; //#endregion //#region src/clients/fetch-failure.d.ts /** * Whatever the request threw, as the error to raise. * * @param args.attempt The request that failed, and how long it had been running. * @param args.error Whatever `fetch` rejected with. * @returns A `VennError` when the failure has a name, and `args.error` itself * when it does not. An aborted request is one of those: a `race` losing is the * language cancelling, not the request failing. */ declare function asRequestError(args: { attempt: Attempt; error: unknown; }): unknown; //#endregion //#region src/plugin.d.ts /** * The `http` namespace: the request verbs, `http.serve`/`http.on`, the `header` * matcher and the types they trade in. * * Requires the `net` capability, so a host without it refuses the plugin at load * time rather than failing mid-flow. The compiler treats it exactly as it treats * a third-party plugin. */ declare const httpPlugin: PluginDefinition; //#endregion //#region src/server/http-server.errors.d.ts /** VN7020: the address the flow asked for is already taken by something else. */ declare function portInUse(args: { port: number; host: string; }): VennError; /** VN7021: the socket refused to bind for any other reason. */ declare function listenFailed(args: { port: number; host: string; cause: string; }): VennError; /** * Whatever the socket threw, as a Venn error: VN7020 for `EADDRINUSE`, VN7021 * for anything else. * * The translation lives at the producer so no caller has to read a `node:net` * errno to know what went wrong. */ declare function asListenError(args: { port: number; host: string; error: unknown; }): VennError; //#endregion //#region src/server/http-server.types.d.ts /** One request a server received, as the language sees it. */ interface ServerRequest { method: string; /** The path with its query string, exactly as it arrived. */ url: string; headers: Record; body: string; } /** What a handler answers with. Every field has a sensible default. */ interface ServerReply { status?: number; headers?: Record; /** A string is sent as-is; anything else is sent as JSON. */ body?: unknown; } /** Called once per request. Returning nothing sends `204 No Content`. */ type RequestHandler = (request: ServerRequest) => ServerReply | undefined | Promise; /** A server that is listening, and how to stop it. */ interface RunningServer { /** The port it actually bound to. Asking for 0 gets one chosen for you. */ readonly port: number; close(): Promise; } /** * Accepting requests over HTTP. * * Two implementations: `createNodeServer` binds a real socket, and * `createMemoryServer` keeps the handler in memory so a test can deliver a * request without a network. The conformance suite is what says they agree. */ interface HttpServer { listen(args: { port: number; host?: string; handle: RequestHandler; }): Promise; } //#endregion //#region src/server/http-server.port.d.ts /** * The port descriptor `http.serve` resolves through `ctx.port(...)`. * * Declares the `net` capability, so a host that cannot bind a socket refuses the * binding at load time rather than mid-flow. */ declare const HttpServerPort: Port; //#endregion //#region src/server/memory-server.d.ts /** A server that is listening in memory, plus the way to knock on its door. */ interface MemoryServer extends RunningServer { /** Deliver a request as though it had arrived over the network. */ deliver(request: Partial): Promise; readonly closed: boolean; } /** The double factory, which keeps every server it started so a test can find it. */ interface MemoryHttpServer extends HttpServer { /** Every server started through this, newest last. */ readonly started: readonly MemoryServer[]; } /** * The double: no socket, no network, no waiting. * * A test starts the flow that serves, hands it a request and reads the reply, * running the same handler the real server would call. Ports are book-kept here * rather than by the operating system, so tests beside each other never collide. */ declare function createMemoryServer(): MemoryHttpServer; //#endregion //#region src/server/on-action.d.ts /** * `http.on server handler`: say what the server answers with. * * The handler is an ordinary `fn`, so everything the language already does * applies inside it: it can call any verb, and it waits for what it reaches for * without saying so. Whatever it returns becomes the reply. Calling `http.on` * again replaces the handler. */ declare function onAction(): ActionDefinition; //#endregion //#region src/server/serve-action.d.ts /** * The value `http.serve` hands back, seen by flows as `http.Server`. * * A server is not a request-response verb: it stays, and the requests arrive * afterwards. So the verb returns something the program holds on to: the port it * got, and what it may do with it. */ interface ServeHandle { kind: "http-server"; port: number; /** Deliver one request to whatever `handle` was last given. Used by tests. */ deliver: (request: Partial) => Promise; close: () => Promise; /** Replace the handler. `http.on` is how a flow reaches this. */ onRequest: (handle: (request: ServerRequest) => unknown) => void; } /** * `let api = http.serve { port: 8080 }`: start listening. * * The handler starts as a 404 and `http.on` replaces it, so a request arriving * before the flow has said what to do with it gets an answer instead of hanging. */ declare function serveAction(): ActionDefinition; //#endregion export { type Attempt, type HttpClient, HttpClientPort, type HttpFailure, type HttpRequest, type HttpResponse, type HttpServer, HttpServerPort, type MemoryHttpServer, type MemoryServer, type RequestHandler, type RunningServer, type ServeHandle, type ServerReply, type ServerRequest, asListenError, asRequestError, connectionRefused, createFakeClient, createFetchClient, createMemoryServer, httpPlugin as default, httpPlugin, hostNotFound, listenFailed, okResponse, onAction, portInUse, portNotAllowed, requestFailed, requestTimedOut, serveAction }; //# sourceMappingURL=index.d.ts.map