UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

245 lines (244 loc) 7.05 kB
/** * Functions for handling HTTP related tasks, such as parsing headers and * serving HTTP requests. * * Many functions in this module are designed to work in all environments, but * some of them are only available in server runtimes such as Node.js, Deno, * Bun and Cloudflare Workers. * * This module itself is a executable script that can be used to serve static * files in the current working directory, or we can provide an entry module * which has an default export that satisfies the {@link ServeOptions} to start * a custom HTTP server. * * The script can be run directly with Deno, Bun, or Node.js. * * Deno: * ```sh * deno run --allow-net --allow-read jsr:@ayonli/jsext/http [--port PORT] [DIR] * deno run --allow-net --allow-read jsr:@ayonli/jsext/http <entry.ts> * ``` * * Bun: * ```sh * bun run node_modules/@ayonli/jsext/http.ts [--port PORT] [DIR] * bun run node_modules/@ayonli/jsext/http.ts <entry.ts> * ``` * * Node.js (tsx): * ```sh * tsx node_modules/@ayonli/jsext/http.ts [--port PORT] [DIR] * tsx node_modules/@ayonli/jsext/http.ts <entry.ts> * ``` * * In Node.js, we can also do this: * * ```sh * tsx --import=@ayonli/jsext/http <entry.ts> [--port PORT] [--parallel [NUM]] * # or * node -r @ayonli/jsext/http <entry.js> [--port PORT] [--parallel [NUM]] * ``` * @module * @experimental */ import { FileInfo } from "./fs.ts"; import { withWeb as _withWeb } from "./http/internal.ts"; import { NetAddress, RequestContext, RequestHandler, RequestErrorHandler, ServeOptions, ServeStaticOptions, Server } from "./http/server.ts"; /** * @deprecated This function has been moved to `@ayonli/jsext/http/internal`. */ export declare const withWeb: typeof _withWeb; export * from "./http/util.ts"; export type { NetAddress, RequestContext, RequestHandler, RequestErrorHandler, ServeOptions, ServeStaticOptions, Server, }; /** * Calculates the ETag for a given entity. * * @example * ```ts * import { stat } from "@ayonli/jsext/fs"; * import { etag } from "@ayonli/jsext/http"; * * const etag1 = await etag("Hello, World!"); * * const data = new Uint8Array([1, 2, 3, 4, 5]); * const etag2 = await etag(data); * * const info = await stat("file.txt"); * const etag3 = await etag(info); * ``` */ export declare function etag(data: string | Uint8Array | FileInfo): Promise<string>; /** * Returns a random port number that is available for listening. * * NOTE: This function is not available in the browser and worker runtimes such * as Cloudflare Workers. * * @param prefer The preferred port number to return if it is available, * otherwise a random port is returned. * * @param hostname The hostname to bind the port to. Default is "0.0.0.0", only * used when `prefer` is set and not `0`. */ export declare function randomPort(prefer?: number | undefined, hostname?: string | undefined): Promise<number>; /** * Serves HTTP requests with the given options. * * This function provides a unified way to serve HTTP requests in all server * runtimes, even worker runtimes. It's similar to the `Deno.serve` and * `Bun.serve` functions, in fact, it calls them internally when running in the * corresponding runtime. When running in Node.js, it uses the built-in `http` * or `http2` modules to create the server. * * This function also provides easy ways to handle Server-sent Events and * WebSockets inside the fetch handler without touching the underlying verbose * APIs. * * Currently, the following runtimes are supported: * * - Node.js (v18.4.1 or above) * - Deno * - Bun * - Cloudflare Workers * - Fastly Compute * - Service Worker in the browser * * NOTE: WebSocket is not supported in Fastly Compute and browser's Service * Worker at the moment. * * @example * ```ts * // simple http server * import { serve } from "@ayonli/jsext/http"; * * serve({ * fetch(req) { * return new Response("Hello, World!"); * }, * }); * ``` * * @example * ```ts * // set the hostname and port * import { serve } from "@ayonli/jsext/http"; * * serve({ * hostname: "localhost", * port: 8787, // same port as Wrangler dev * fetch(req) { * return new Response("Hello, World!"); * }, * }); * ``` * * @example * ```ts * // serve HTTPS/HTTP2 requests * import { readFileAsText } from "@ayonli/jsext/fs"; * import { serve } from "@ayonli/jsext/http"; * * serve({ * key: await readFileAsText("./cert.key"), * cert: await readFileAsText("./cert.pem"), * fetch(req) { * return new Response("Hello, World!"); * }, * }); * ``` * * @example * ```ts * // respond Server-sent Events * import { serve } from "@ayonli/jsext/http"; * * serve({ * fetch(req, ctx) { * const { events, response } = ctx.createEventEndpoint(); * let count = events.lastEventId ? Number(events.lastEventId) : 0; * * setInterval(() => { * const lastEventId = String(++count); * events.dispatchEvent(new MessageEvent("ping", { * data: lastEventId, * lastEventId, * })); * }, 5_000); * * return response; * }, * }); * ``` * * @example * ```ts * // upgrade to WebSocket * import { serve } from "@ayonli/jsext/http"; * * serve({ * fetch(req, ctx) { * const { socket, response } = ctx.upgradeWebSocket(); * * socket.addEventListener("message", (event) => { * console.log(event.data); * socket.send("Hello, Client!"); * }); * * return response; * }, * }); * ``` * * @example * ```ts * // module mode (for `deno serve`, Bun and Cloudflare Workers) * import { serve } from "@ayonli/jsext/http"; * * export default serve({ * type: "module", * fetch(req) { * return new Response("Hello, World!"); * }, * }); * ``` */ export declare function serve(options: ServeOptions): Server; /** * Serves static files from a file system directory or KV namespace (in * Cloudflare Workers). * * NOTE: In Node.js, this function requires Node.js v18.4.1 or above. * * NOTE: In Cloudflare Workers, this function requires setting the * `[site].bucket` option in the `wrangler.toml` file. * * @example * ```ts * import { serve, serveStatic } from "@ayonli/jsext/http"; * * // use `serve()` so this program runs in all environments * serve({ * async fetch(req: Request, ctx) { * const { pathname } = new URL(req.url); * * if (pathname.startsWith("/assets")) { * return await serveStatic(req, { * fsDir: "./assets", * kv: ctx.bindings?.__STATIC_CONTENT, * urlPrefix: "/assets", * }); * } * * return new Response("Hello, World!"); * } * }); * ``` * * @example * ```toml * # wrangler.toml * [site] * bucket = "./assets" * ``` */ export declare function serveStatic(req: Request, options?: ServeStaticOptions): Promise<Response>;