@adonix.org/cloud-spark 0.0.193 → 0.0.195

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants/index.ts","../src/constants/cache.ts","../src/constants/methods.ts","../src/constants/time.ts","../src/guards/basic.ts","../src/guards/cache.ts","../src/middleware/cache/keys.ts","../src/middleware/cache/policy.ts","../src/middleware/cache/utils.ts","../src/constants/headers.ts","../src/utils/compare.ts","../src/utils/headers.ts","../src/middleware/cache/rules/utils.ts","../src/middleware/cache/rules/control.ts","../src/errors.ts","../src/guards/methods.ts","../src/middleware/websocket/constants.ts","../src/responses.ts","../src/constants/media.ts","../src/guards/responses.ts","../src/utils/media.ts","../src/middleware/cache/rules/validation.ts","../src/middleware/cache/rules/etag.ts","../src/middleware/cache/rules/method.ts","../src/middleware/cache/rules/modified.ts","../src/middleware/cache/rules/range.ts","../src/middleware/cache/rules/security.ts","../src/middleware/cache/rules/upgrade.ts","../src/middleware/cache/variant.ts","../src/middleware/cache/handler.ts","../src/middleware/cache/cache.ts","../src/guards/cors.ts","../src/middleware/cors/constants.ts","../src/middleware/cors/utils.ts","../src/middleware/cors/handler.ts","../src/middleware/cors/cors.ts","../src/middleware/websocket/handler.ts","../src/middleware/websocket/utils.ts","../src/middleware/websocket/websocket.ts","../src/guards/websocket.ts","../src/websocket/events.ts","../src/websocket/constants.ts","../src/websocket/utils.ts","../src/websocket/base.ts","../src/websocket/new.ts","../src/websocket/restore.ts","../src/websocket/sessions.ts","../src/guards/middleware.ts","../src/workers/base.ts","../src/workers/middleware.ts","../src/workers/basic.ts","../src/routes.ts","../src/workers/route.ts"],"sourcesContent":["/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * https://github.com/prettymuchbryce/http-status-codes\n */\nexport { StatusCodes } from \"http-status-codes\";\n\nexport * from \"./cache\";\nexport * from \"./methods\";\nexport * from \"./time\";\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport CacheLib from \"cache-control-parser\";\n\n/**\n * @see {@link https://github.com/etienne-martin/cache-control-parser | cache-control-parser}\n */\nexport type CacheControl = CacheLib.CacheControl;\nexport const CacheControl = {\n parse: CacheLib.parse,\n stringify: CacheLib.stringify,\n\n /** A CacheControl directive that disables all caching. */\n DISABLE: Object.freeze({\n \"no-cache\": true,\n \"no-store\": true,\n \"must-revalidate\": true,\n \"max-age\": 0,\n }) satisfies CacheControl,\n};\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Standard HTTP request methods.\n */\nexport enum Method {\n GET = \"GET\",\n PUT = \"PUT\",\n HEAD = \"HEAD\",\n POST = \"POST\",\n PATCH = \"PATCH\",\n DELETE = \"DELETE\",\n OPTIONS = \"OPTIONS\",\n}\n\n/**\n * Shorthand constants for each HTTP method.\n *\n * These are equivalent to the corresponding enum members in `Method`.\n * For example, `GET === Method.GET`.\n */\nexport const { GET, PUT, HEAD, POST, PATCH, DELETE, OPTIONS } = Method;\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Time constants in seconds. Month is approximated as 30 days.\n */\nexport const Time = {\n Second: 1,\n Minute: 60,\n Hour: 3600, // 60 * 60\n Day: 86400, // 60 * 60 * 24\n Week: 604800, // 60 * 60 * 24 * 7\n Month: 2592000, // 60 * 60 * 24 * 30\n Year: 31536000, // 60 * 60 * 24 * 365\n} as const;\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Checks if the provided value is an array of strings.\n *\n * @param value - The value to check.\n * @returns True if `array` is an array where every item is a string.\n */\nexport function isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\n/**\n * Checks if a value is a string.\n *\n * @param value - The value to check.\n * @returns `true` if the value is a string, otherwise `false`.\n */\nexport function isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\n\n/**\n * Checks if a value is a valid number (not NaN).\n *\n * This function returns `true` if the value is of type `number`\n * and is not `NaN`. It works as a type guard for TypeScript.\n *\n * @param value - The value to check.\n * @returns `true` if the value is a number and not `NaN`, otherwise `false`.\n */\nexport function isNumber(value: unknown): value is number {\n return typeof value === \"number\" && !Number.isNaN(value);\n}\n\n/**\n * Checks if a value is a boolean.\n *\n * @param value - The value to check.\n * @returns `true` if the value is a boolean (`true` or `false`), otherwise `false`.\n */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === \"boolean\";\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CacheInit } from \"../interfaces/cache\";\n\nimport { isString } from \"./basic\";\n\n/**\n * Asserts that a value is a valid {@link CacheInit} object.\n *\n * Ensures that if provided, `name` is a string and `getKey` is a function.\n *\n * @param value - The value to check.\n * @throws TypeError If the object shape is invalid.\n */\nexport function assertCacheInit(value: unknown): asserts value is CacheInit {\n if (typeof value !== \"object\" || value === null) {\n throw new TypeError(\"CacheInit must be an object.\");\n }\n\n const { name, getKey } = value as Partial<CacheInit>;\n\n assertCacheName(name);\n assertGetKey(getKey);\n}\n\n/**\n * Asserts that a value is a string suitable for a cache name.\n *\n * If the value is `undefined`, this function does nothing.\n * Otherwise, it throws a `TypeError` if the value is not a string.\n *\n * @param value - The value to check.\n * @throws TypeError If the value is defined but not a string.\n */\nexport function assertCacheName(value: unknown): asserts value is string | undefined {\n if (value === undefined) return;\n if (!isString(value)) {\n throw new TypeError(\"Cache name must be a string.\");\n }\n}\n\n/**\n * Asserts that a value is a function suitable for `getKey`.\n *\n * If the value is `undefined`, this function does nothing.\n * Otherwise, it throws a `TypeError` if the value is not a function.\n *\n * @param value - The value to check.\n * @throws TypeError If the value is defined but not a function.\n */\nexport function assertGetKey(\n value: unknown,\n): asserts value is (request: Request) => URL | undefined {\n if (value === undefined) return;\n if (typeof value !== \"function\") {\n throw new TypeError(\"getKey must be a function.\");\n }\n}\n\n/**\n * Asserts that a value is a `URL` instance suitable for use as a cache key.\n *\n * If the value is not a `URL`, this function throws a `TypeError`.\n *\n * @param value - The value to check.\n * @throws TypeError If the value is not a `URL`.\n */\nexport function assertKey(value: unknown): asserts value is URL {\n if (!(value instanceof URL)) {\n throw new TypeError(\"getKey must return a URL.\");\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n *\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Returns a new URL with its query parameters sorted into a stable order.\n *\n * This is used for cache key generation: URLs that differ only in the\n * order of their query parameters will normalize to the same key.\n *\n * @param request - The incoming Request whose URL will be normalized.\n * @returns A new URL with query parameters sorted by name.\n */\nexport function sortSearchParams(request: Request): URL {\n const url = new URL(request.url);\n url.searchParams.sort();\n url.hash = \"\";\n return url;\n}\n\n/**\n * Returns a new URL with all query parameters removed.\n *\n * This is used when query parameters are not relevant to cache lookups,\n * ensuring that variants of the same resource share a single cache entry.\n *\n * @param request - The incoming Request whose URL will be normalized.\n * @returns A new URL with no query parameters.\n */\nexport function stripSearchParams(request: Request): URL {\n const url = new URL(request.url);\n url.search = \"\";\n url.hash = \"\";\n return url;\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Worker } from \"../../interfaces/worker\";\n\nimport { CacheRule } from \"./rules/interfaces\";\n\n/**\n * Represents a cache policy, defining the rules that determine\n * whether a cached response can be used.\n *\n * The `CachePolicy` executes its rules in order, passing the cached\n * response through a chain of validators. Each rule can:\n * - Return the cached response if eligible,\n * - Transform it (e.g., for `HEAD` requests), or\n * - Return `undefined` to indicate the cache cannot be used.\n *\n * The policy **does not fetch from origin**; it only evaluates the cache.\n */\nexport class CachePolicy {\n private readonly rules: CacheRule[] = [];\n\n /**\n * Adds one or more cache rules to the policy.\n *\n * @param rules - One or more `CacheRule` instances to apply.\n * @returns `this` for chaining.\n */\n public use(...rules: CacheRule[]): this {\n this.rules.push(...rules);\n return this;\n }\n\n /**\n * Executes the cache rules in order to determine cache eligibility.\n *\n * Each rule receives the cached response (or `undefined`) from the\n * next rule in the chain. If all rules pass, the cached response is returned.\n *\n * @param worker - The worker context containing the request.\n * @param getCached - Function returning the cached response if available.\n * @returns The cached response if allowed by all rules, or `undefined`\n * if the cache cannot be used.\n */\n public execute(\n worker: Worker,\n getCached: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n const chain = this.rules.reduceRight(\n (next, rule) => () => rule.apply(worker, next),\n () => getCached(),\n );\n return chain();\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n *\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StatusCodes } from \"http-status-codes\";\n\nimport { CacheControl, GET } from \"../../constants\";\nimport { HttpHeader } from \"../../constants/headers\";\nimport { lexCompare } from \"../../utils/compare\";\nimport { getHeaderValues } from \"../../utils/headers\";\n\n/** Base URL used for constructing cache keys. Only used internally. */\nconst VARY_CACHE_URL = \"https://vary\";\n\n/**\n * Wildcard member (`*`) for the `Vary` header.\n *\n * When present, it indicates that the response can vary based on unspecified\n * request headers. Such a response **MUST NOT be stored by a shared cache**,\n * since it cannot be reliably reused for any request.\n *\n * Example:\n * ```http\n * Vary: *\n * ```\n */\nconst VARY_WILDCARD = \"*\";\n\n/**\n * Determines whether a given Response is safe to cache.\n *\n * Internal utility used by the caching pipeline to decide if a response\n * should be stored in the cache. Returns `true` only if:\n *\n * - The response status is `200 OK`.\n * - The request method is `GET`.\n * - The response does not have a `Vary` header containing `*`.\n * - The response has TTL specified in max-age or s-maxage.\n * - Neither the request nor the response has `Cache-Control: no-store`.\n * - The response is not marked `private` and does not specify `max-age=0`.\n * - The request does **not** include sensitive headers such as `Authorization` or `Cookie`.\n * - The response does **not** include a `Set-Cookie` header.\n * - The response does not include a `Content-Range` header (partial content).\n *\n * These checks collectively ensure that the response is publicly cacheable,\n * consistent with Cloudflare's and general HTTP caching rules.\n *\n * @param request - The incoming Request object.\n * @param response - The Response object generated for the request.\n * @returns `true` if the response can safely be cached; `false` otherwise.\n * @throws Error If a 200 OK response contains a Content-Range header.\n */\nexport function isCacheable(request: Request, response: Response): boolean {\n if (response.status !== StatusCodes.OK) return false;\n if (request.method !== GET) return false;\n\n if (request.headers.has(HttpHeader.AUTHORIZATION)) return false;\n if (request.headers.has(HttpHeader.COOKIE)) return false;\n\n const requestCacheControl = getCacheControl(request.headers);\n if (requestCacheControl[\"no-store\"]) return false;\n\n if (!response.headers.has(HttpHeader.CACHE_CONTROL)) return false;\n const responseCacheControl = getCacheControl(response.headers);\n const ttl = responseCacheControl[\"s-maxage\"] ?? responseCacheControl[\"max-age\"];\n if (ttl === undefined || ttl === 0) return false;\n if (responseCacheControl[\"no-store\"]) return false;\n if (responseCacheControl[\"no-cache\"]) return false;\n if (responseCacheControl[\"private\"]) return false;\n\n if (response.headers.has(HttpHeader.SET_COOKIE)) return false;\n if (getVaryHeader(response).includes(VARY_WILDCARD)) return false;\n\n if (response.headers.has(HttpHeader.INTERNAL_VARIANT_SET)) {\n throw new Error(\"Found conflicting vary header.\");\n }\n\n if (response.headers.has(HttpHeader.CONTENT_RANGE)) {\n throw new Error(\"Found content-range header on 200 OK. Must use 206 Partial Content\");\n }\n\n return true;\n}\n\n/**\n * Parses the Cache-Control header from the given headers.\n *\n * @param headers - The request headers to inspect.\n * @returns A `CacheControl` object.\n */\nexport function getCacheControl(headers: Headers): CacheControl {\n return CacheControl.parse(headers.get(HttpHeader.CACHE_CONTROL) ?? \"\");\n}\n\n/**\n * Extracts and normalizes the `Vary` header from a Response.\n * - Splits comma-separated values\n * - Deduplicates\n * - Converts all values to lowercase\n * - Sorts lexicographically\n * - Removes `Vary` headers to ignore for caching.\n *\n * @param response The Response object containing headers.\n * @returns An array of normalized header names from the Vary header.\n */\nexport function getVaryHeader(response: Response): string[] {\n return getFilteredVary(getHeaderValues(response.headers, HttpHeader.VARY));\n}\n\n/**\n * Filters out headers that should be ignored for caching, currently:\n * - `Accept-Encoding` (handled automatically by the platform)\n *\n * @param vary Array of normalized Vary header names.\n * @returns Array of headers used for computing cache variations.\n */\nexport function getFilteredVary(vary: string[]): string[] {\n const values = vary\n .map((h) => h.toLowerCase())\n .filter((value) => value !== HttpHeader.ACCEPT_ENCODING)\n .sort(lexCompare);\n return Array.from(new Set(values));\n}\n\n/**\n * Generates a Vary-aware cache key for a request.\n *\n * The key is based on:\n * 1. The provided `key` URL, which is normalized by default but can be fully customized\n * by the caller. For example, users can:\n * - Sort query parameters\n * - Remove the search/query string entirely\n * - Exclude certain query parameters\n * This allows full control over how this cache key is generated.\n * 2. The request headers listed in `vary` (after filtering and lowercasing).\n *\n * Behavior:\n * - Headers in `vary` are sorted and included in the key.\n * - The combination of the key URL and header values is base64-encoded to produce\n * a safe cache key.\n * - The resulting string is returned as an absolute URL rooted at `VARY_CACHE_URL`.\n *\n * @param request The Request object used to generate the key.\n * @param vary Array of header names from the `Vary` header that affect caching.\n * @param key The cache key to be used for this request. Can be modified by the caller for\n * custom cache key behavior.\n * @returns A URL representing a unique cache key for this request + Vary headers.\n */\nexport function getVaryKey(request: Request, vary: string[], key: URL): string {\n const varyPairs: [string, string][] = [];\n const filtered = getFilteredVary(vary);\n\n for (const header of filtered) {\n const value = request.headers.get(header);\n if (value !== null) {\n varyPairs.push([header, normalizeVaryValue(header, value)]);\n }\n }\n\n const encoded = base64UrlEncode(JSON.stringify([key.toString(), varyPairs]));\n return new URL(encoded, VARY_CACHE_URL).toString();\n}\n\n/**\n * Normalizes the value of a header used in a Vary key.\n *\n * Only lowercases headers that are defined as case-insensitive\n * by HTTP standards and commonly used for content negotiation.\n *\n * @param name - The header name (case-insensitive).\n * @param value - The header value as received from the request.\n * @returns The normalized header value.\n */\nexport function normalizeVaryValue(name: string, value: string): string {\n switch (name.toLowerCase()) {\n case HttpHeader.ACCEPT:\n case HttpHeader.ACCEPT_LANGUAGE:\n case HttpHeader.ORIGIN:\n return value.toLowerCase();\n default:\n return value;\n }\n}\n\n/**\n * Encodes a string as URL-safe Base64.\n * - Converts to UTF-8 bytes\n * - Base64-encodes\n * - Replaces `+` with `-` and `/` with `_`\n * - Removes trailing `=`\n *\n * @param str The input string to encode.\n * @returns URL-safe Base64 string.\n */\nexport function base64UrlEncode(str: string): string {\n const utf8 = new TextEncoder().encode(str);\n let binary = \"\";\n for (const byte of utf8) {\n binary += String.fromCodePoint(byte);\n }\n return btoa(binary)\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/={1,2}$/, \"\");\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Internally used headers.\n */\nexport const HttpHeader = {\n ACCEPT: \"accept\",\n ACCEPT_ENCODING: \"accept-encoding\",\n ACCEPT_LANGUAGE: \"accept-language\",\n ACCEPT_RANGES: \"accept-ranges\",\n ALLOW: \"allow\",\n AUTHORIZATION: \"authorization\",\n CACHE_CONTROL: \"cache-control\",\n CONNECTION: \"connection\",\n CONTENT_DISPOSITION: \"content-disposition\",\n CONTENT_ENCODING: \"content-encoding\",\n CONTENT_LANGUAGE: \"content-language\",\n CONTENT_LENGTH: \"content-length\",\n CONTENT_RANGE: \"content-range\",\n CONTENT_TYPE: \"content-type\",\n CONTENT_MD5: \"content-md5\",\n COOKIE: \"cookie\",\n ETAG: \"etag\",\n IF_MATCH: \"if-match\",\n IF_MODIFIED_SINCE: \"if-modified-since\",\n IF_NONE_MATCH: \"if-none-match\",\n IF_UNMODIFIED_SINCE: \"if-unmodified-since\",\n LAST_MODIFIED: \"last-modified\",\n ORIGIN: \"origin\",\n RANGE: \"range\",\n SET_COOKIE: \"set-cookie\",\n VARY: \"vary\",\n\n // Cors Headers\n ACCESS_CONTROL_ALLOW_CREDENTIALS: \"access-control-allow-credentials\",\n ACCESS_CONTROL_ALLOW_HEADERS: \"access-control-allow-headers\",\n ACCESS_CONTROL_ALLOW_METHODS: \"access-control-allow-methods\",\n ACCESS_CONTROL_ALLOW_ORIGIN: \"access-control-allow-origin\",\n ACCESS_CONTROL_EXPOSE_HEADERS: \"access-control-expose-headers\",\n ACCESS_CONTROL_MAX_AGE: \"access-control-max-age\",\n\n // Websocket Headers\n SEC_WEBSOCKET_VERSION: \"sec-websocket-version\",\n UPGRADE: \"upgrade\",\n\n // Internal Headers\n INTERNAL_VARIANT_SET: \"internal-variant-set\",\n} as const;\n\n/**\n * Headers that must not be sent in 304 Not Modified responses.\n * These are stripped to comply with the HTTP spec.\n */\nexport const FORBIDDEN_304_HEADERS = [\n HttpHeader.CONTENT_TYPE,\n HttpHeader.CONTENT_LENGTH,\n HttpHeader.CONTENT_RANGE,\n HttpHeader.CONTENT_ENCODING,\n HttpHeader.CONTENT_LANGUAGE,\n HttpHeader.CONTENT_DISPOSITION,\n HttpHeader.CONTENT_MD5,\n];\n\n/**\n * Headers that should not be sent in 204 No Content responses.\n * Stripping them is recommended but optional per spec.\n */\nexport const FORBIDDEN_204_HEADERS = [HttpHeader.CONTENT_LENGTH, HttpHeader.CONTENT_RANGE];\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Lexicographically compares two strings.\n *\n * This comparator can be used in `Array.prototype.sort()` to produce a\n * consistent, stable ordering of string arrays.\n *\n * @param a - The first string to compare.\n * @param b - The second string to compare.\n * @returns A number indicating the relative order of `a` and `b`.\n */\nexport function lexCompare(a: string, b: string): number {\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lexCompare } from \"./compare\";\n\n/**\n * Sets a header on the given Headers object.\n *\n * - If `value` is an array, any duplicates and empty strings are removed.\n * - If the resulting value is empty, the header is deleted.\n * - Otherwise, values are joined with `\", \"` and set as the header value.\n *\n * @param headers - The Headers object to modify.\n * @param key - The header name to set.\n * @param value - The header value(s) to set. Can be a string or array of strings.\n */\nexport function setHeader(headers: Headers, key: string, value: string | string[]): void {\n const raw = Array.isArray(value) ? value : [value];\n const values = Array.from(new Set(raw.map((v) => v.trim())))\n .filter((v) => v.length)\n .sort(lexCompare);\n\n if (!values.length) {\n headers.delete(key);\n return;\n }\n\n headers.set(key, values.join(\", \"));\n}\n\n/**\n * Merges new value(s) into an existing header on the given Headers object.\n *\n * - Preserves any existing values and adds new ones.\n * - Removes duplicates and trims all values.\n * - If the header does not exist, it is created.\n * - If the resulting value array is empty, the header is deleted.\n *\n * @param headers - The Headers object to modify.\n * @param key - The header name to merge into.\n * @param value - The new header value(s) to add. Can be a string or array of strings.\n */\nexport function mergeHeader(headers: Headers, key: string, value: string | string[]): void {\n const values = Array.isArray(value) ? value : [value];\n if (values.length === 0) return;\n\n const existing = getHeaderValues(headers, key);\n const merged = existing.concat(values.map((v) => v.trim()));\n\n setHeader(headers, key, merged);\n}\n\n/**\n * Returns the values of an HTTP header as an array of strings.\n *\n * This helper:\n * - Retrieves the header value by `key`.\n * - Splits the value on commas.\n * - Trims surrounding whitespace from each entry.\n * - Filters out any empty tokens.\n * - Removes duplicate values (case-sensitive)\n *\n * If the header is not present, an empty array is returned.\n *\n */\nexport function getHeaderValues(headers: Headers, key: string): string[] {\n const values =\n headers\n .get(key)\n ?.split(\",\")\n .map((v) => v.trim())\n .filter((v) => v.length > 0) ?? [];\n return Array.from(new Set(values)).sort(lexCompare);\n}\n\n/**\n * Removes a list of header fields from a {@link Headers} object.\n *\n * @param headers - The {@link Headers} object to modify in place.\n * @param keys - An array of header field names to remove. Header names are\n * matched case-insensitively per the Fetch spec.\n */\nexport function filterHeaders(headers: Headers, keys: string[]): void {\n for (const key of keys) {\n headers.delete(key);\n }\n}\n\n/**\n * Extracts all header names from a `Headers` object, normalizes them,\n * and returns them in a stable, lexicographically sorted array.\n *\n * @param headers - The `Headers` object to extract keys from.\n * @returns A sorted array of lowercase header names.\n */\nexport function getHeaderKeys(headers: Headers): string[] {\n return [...headers.keys()].sort(lexCompare);\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../../constants/headers\";\nimport { isNumber, isString } from \"../../../guards/basic\";\nimport { getHeaderValues } from \"../../../utils/headers\";\n\nimport { ByteRange, CacheValidators } from \"./interfaces\";\n\nconst RANGE_REGEX = /^bytes=(\\d{1,12})-(\\d{0,12})$/;\nconst ETAG_WEAK_PREFIX = \"W/\";\nconst WILDCARD_ETAG = \"*\";\n\n/**\n * Parses the `Range` header from an HTTP request and returns a byte range object.\n *\n * Only supports **single-range headers** of the form `bytes=X-Y` or `bytes=X-`.\n * - `X` (start) is **required** and must be a whole number (up to 12 digits).\n * - `Y` (end) is optional; if missing, `end` is `undefined`.\n *\n * @param request - The HTTP request object containing headers.\n * @returns A `ByteRange` object with `start` and optional `end` if valid; otherwise `undefined`.\n */\nexport function getRange(request: Request): ByteRange | undefined {\n const range = request.headers.get(HttpHeader.RANGE);\n if (!range) return;\n\n const match = RANGE_REGEX.exec(range);\n if (!match) return;\n\n const start = Number(match[1]);\n const end = match[2] === \"\" ? undefined : Number(match[2]);\n\n return end === undefined ? { start } : { start, end };\n}\n\n/**\n * Evaluates an `If-Match` precondition against the current ETag.\n *\n * Returns `true` when the precondition fails, meaning the resource’s\n * current ETag does **not** match any of the supplied `If-Match` values\n * and the request should return **412 Precondition Failed**.\n *\n * @param ifMatch - Parsed `If-Match` header values.\n * @param etag - Current entity tag for the resource.\n * @returns `true` if the precondition fails; otherwise `false`.\n */\nexport function isPreconditionFailed(ifMatch: string[], etag: string): boolean {\n return ifMatch.length > 0 && !found(ifMatch, etag, WILDCARD_ETAG);\n}\n\n/**\n * Evaluates an `If-None-Match` precondition against the current ETag.\n *\n * Returns `true` when the resource has **not** been modified since the\n * validator was issued — i.e., when the normalized ETag matches one of\n * the `If-None-Match` values or the wildcard `\"*\"`.\n *\n * @param ifNoneMatch - Parsed `If-None-Match` header values.\n * @param etag - Current entity tag for the resource.\n * @returns `true` if the response should return **304 Not Modified**; otherwise `false`.\n */\nexport function isNotModified(ifNoneMatch: string[], etag: string): boolean {\n return found(ifNoneMatch, normalizeEtag(etag), WILDCARD_ETAG);\n}\n\n/**\n * Determines whether any of the given search values appear in the array.\n *\n * @param array - The array to search.\n * @param search - One or more values to look for.\n * @returns `true` if any search value is found in the array; otherwise `false`.\n */\nexport function found(array: string[], ...search: string[]): boolean {\n return array.some((value) => search.includes(value));\n}\n\n/**\n * Parses a date string into a timestamp (milliseconds since epoch).\n *\n * Returns `undefined` for invalid, null, or non-string values.\n *\n * @param value - The date string to parse.\n * @returns Parsed timestamp if valid; otherwise `undefined`.\n */\nexport function toDate(value: string | null | undefined): number | undefined {\n if (!isString(value)) return undefined;\n\n const date = Date.parse(value);\n return Number.isNaN(date) ? undefined : date;\n}\n\n/**\n * Normalizes an ETag for equality comparison.\n *\n * Weak ETags (`W/\"etag\"`) are converted to their strong form by removing\n * the leading `W/` prefix. Strong ETags are returned unchanged.\n *\n * @param etag - The entity tag to normalize.\n * @returns The normalized ETag string.\n */\nexport function normalizeEtag(etag: string): string {\n return etag.startsWith(ETAG_WEAK_PREFIX) ? etag.slice(2) : etag;\n}\n\n/**\n * Extracts cache validator headers from a request.\n *\n * Returns an object containing all standard conditional request headers:\n * - `If-Match` (weak validators removed)\n * - `If-None-Match` (normalized)\n * - `If-Modified-Since`\n * - `If-Unmodified-Since`\n *\n * @param headers - The headers object from which to extract validators.\n * @returns A `CacheValidators` structure containing parsed header values.\n */\nexport function getCacheValidators(headers: Headers): CacheValidators {\n return {\n ifMatch: getHeaderValues(headers, HttpHeader.IF_MATCH).filter(\n (value) => !value.startsWith(ETAG_WEAK_PREFIX),\n ),\n ifNoneMatch: getHeaderValues(headers, HttpHeader.IF_NONE_MATCH).map(normalizeEtag),\n ifModifiedSince: headers.get(HttpHeader.IF_MODIFIED_SINCE),\n ifUnmodifiedSince: headers.get(HttpHeader.IF_UNMODIFIED_SINCE),\n };\n}\n\n/**\n * Returns true if any cache validator headers are present.\n *\n * Useful as a quick check for conditional requests where the\n * specific values are not important.\n *\n * @param headers - The request headers to inspect.\n * @returns `true` if any validator exists; otherwise `false`.\n */\nexport function hasCacheValidator(headers: Headers): boolean {\n const { ifNoneMatch, ifMatch, ifModifiedSince, ifUnmodifiedSince } =\n getCacheValidators(headers);\n return (\n ifNoneMatch.length > 0 ||\n ifMatch.length > 0 ||\n ifModifiedSince !== null ||\n ifUnmodifiedSince !== null\n );\n}\n\n/**\n * Safely extracts the `Content-Length` header value.\n *\n * Returns the length as a number if present and valid. Returns `undefined`\n * if the header is missing, empty, or not a valid number.\n *\n * @param headers - The headers object to read from.\n * @returns Parsed content length if valid; otherwise `undefined`.\n */\nexport function getContentLength(headers: Headers): number | undefined {\n const lengthHeader = headers.get(HttpHeader.CONTENT_LENGTH);\n if (lengthHeader === null) return;\n if (lengthHeader.trim() === \"\") return;\n\n const length = Number(lengthHeader);\n if (!isNumber(length)) return;\n\n return length;\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Worker } from \"../../../interfaces\";\nimport { getCacheControl } from \"../utils\";\n\nimport { CacheRule } from \"./interfaces\";\nimport { hasCacheValidator } from \"./utils\";\n\n/**\n * Determines cache eligibility based on request `Cache-Control` headers.\n *\n * - `no-store` always prevents using the cache.\n * - `no-cache` or `max-age=0` prevent using the cache **unless** conditional validators\n * (e.g., `If-None-Match`, `If-Modified-Since`) are present.\n * - Otherwise, the request passes to the next rule in the chain.\n */\nexport class CacheControlRule implements CacheRule {\n /**\n * Applies cache-control header validation to determine cache usability.\n *\n * @param worker - The worker context containing the request.\n * @param next - Function invoking the next cache rule or returning a cached response.\n * @returns The cached response if allowed by cache-control, or `undefined` if the cache cannot be used.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n const cache = getCacheControl(worker.request.headers);\n\n if (cache[\"no-store\"]) {\n return undefined;\n }\n\n if (\n (cache[\"no-cache\"] || cache[\"max-age\"] === 0) &&\n !hasCacheValidator(worker.request.headers)\n ) {\n return undefined;\n }\n\n return next();\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getReasonPhrase, StatusCodes } from \"http-status-codes\";\n\nimport { CacheControl } from \"./constants/cache\";\nimport { HttpHeader } from \"./constants/headers\";\nimport { assertMethods } from \"./guards/methods\";\nimport { ErrorJson } from \"./interfaces/error\";\nimport { Worker } from \"./interfaces/worker\";\nimport { WS_VERSION } from \"./middleware/websocket/constants\";\nimport { JsonResponse } from \"./responses\";\n\n/**\n * Generic HTTP error response.\n * Sends a JSON body with status, error message, and details.\n */\nexport class HttpError extends JsonResponse {\n /**\n * @param worker The worker handling the request.\n * @param status HTTP status code.\n * @param details Optional detailed error message.\n */\n constructor(\n status: StatusCodes,\n protected readonly details?: string,\n ) {\n const json: ErrorJson = {\n status,\n error: getReasonPhrase(status),\n details: details ?? \"\",\n };\n super(json, CacheControl.DISABLE, status);\n }\n}\n\n/** 400 Bad Request error response. */\nexport class BadRequest extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.BAD_REQUEST, details);\n }\n}\n\n/** 401 Unauthorized error response. */\nexport class Unauthorized extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.UNAUTHORIZED, details);\n }\n}\n\n/** 403 Forbidden error response. */\nexport class Forbidden extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.FORBIDDEN, details);\n }\n}\n\n/** 404 Not Found error response. */\nexport class NotFound extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.NOT_FOUND, details);\n }\n}\n\n/** 405 Method Not Allowed error response. */\nexport class MethodNotAllowed extends HttpError {\n constructor(worker: Worker) {\n const methods = worker.getAllowedMethods();\n assertMethods(methods);\n\n super(StatusCodes.METHOD_NOT_ALLOWED, `${worker.request.method} method not allowed.`);\n this.setHeader(HttpHeader.ALLOW, methods);\n }\n}\n\n/** 412 Precondition Failed error response */\nexport class PreconditionFailed extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.PRECONDITION_FAILED, details);\n }\n}\n\n/** 426 Upgrade Required error response. */\nexport class UpgradeRequired extends HttpError {\n constructor() {\n super(StatusCodes.UPGRADE_REQUIRED);\n this.setHeader(HttpHeader.SEC_WEBSOCKET_VERSION, WS_VERSION);\n }\n}\n\n/** 500 Internal Server Error response. */\nexport class InternalServerError extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.INTERNAL_SERVER_ERROR, details);\n }\n}\n\n/** 501 Not Implemented error response. */\nexport class NotImplemented extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.NOT_IMPLEMENTED, details);\n }\n}\n\n/** 501 Method Not Implemented error response for unsupported HTTP methods. */\nexport class MethodNotImplemented extends NotImplemented {\n constructor(worker: Worker) {\n super(`${worker.request.method} method not implemented.`);\n }\n}\n\n/** 503 Service Unavailable error response. */\nexport class ServiceUnavailable extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.SERVICE_UNAVAILABLE, details);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Method } from \"../constants/methods\";\n\nimport { isString } from \"./basic\";\n\n/**\n * A set containing all supported HTTP methods.\n *\n * Useful for runtime checks like validating request methods.\n */\nconst METHOD_SET: Set<string> = new Set(Object.values(Method));\n\n/**\n * Type guard that checks if a string is a valid HTTP method.\n *\n * @param value - The string to test.\n * @returns True if `value` is a recognized HTTP method.\n */\nexport function isMethod(value: unknown): value is Method {\n return isString(value) && METHOD_SET.has(value);\n}\n\n/**\n * Checks if a value is an array of valid HTTP methods.\n *\n * Each element is verified using the `isMethod` type guard.\n *\n * @param value - The value to check.\n * @returns `true` if `value` is an array and every element is a valid `Method`, otherwise `false`.\n */\nexport function isMethodArray(value: unknown): value is Method[] {\n return Array.isArray(value) && value.every(isMethod);\n}\n\n/**\n * Asserts that a value is an array of valid HTTP methods.\n *\n * This function uses {@link isMethodArray} to validate the input. If the\n * value is not an array of `Method` elements, it throws a `TypeError`.\n * Otherwise, TypeScript will narrow the type of `value` to `Method[]`\n * within the calling scope.\n *\n * @param value - The value to check.\n * @throws TypeError If `value` is not a valid {@link Method} array.\n */\nexport function assertMethods(value: unknown): asserts value is Method[] {\n if (!isMethodArray(value)) {\n const desc = Array.isArray(value) ? JSON.stringify(value) : String(value);\n throw new TypeError(`Invalid method array: ${desc}`);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** WebSocket upgrade header value */\nexport const WS_UPGRADE = \"upgrade\";\n\n/** WebSocket protocol header value */\nexport const WS_WEBSOCKET = \"websocket\";\n\n/** WebSocket protocol version */\nexport const WS_VERSION = \"13\";\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getReasonPhrase, StatusCodes } from \"http-status-codes\";\n\nimport { CacheControl } from \"./constants/cache\";\nimport { FORBIDDEN_204_HEADERS, FORBIDDEN_304_HEADERS, HttpHeader } from \"./constants/headers\";\nimport { MediaType, UTF8_CHARSET } from \"./constants/media\";\nimport { GET, HEAD } from \"./constants/methods\";\nimport { assertMethods } from \"./guards/methods\";\nimport { assertOctetStreamInit } from \"./guards/responses\";\nimport { Worker } from \"./interfaces\";\nimport { OctetStreamInit } from \"./interfaces/response\";\nimport { filterHeaders, mergeHeader, setHeader } from \"./utils/headers\";\nimport { withCharset } from \"./utils/media\";\n\n/**\n * Base class for building HTTP responses.\n * Manages headers, status, and media type.\n */\nabstract class BaseResponse {\n /** HTTP headers for the response. */\n public headers: Headers = new Headers();\n\n /** HTTP status code (default 200 OK). */\n public status: StatusCodes = StatusCodes.OK;\n\n /** Optional status text. Defaults to standard reason phrase. */\n public statusText?: string;\n\n /** Optional websocket property. */\n public webSocket?: WebSocket | null;\n\n /** Default media type of the response body. */\n public mediaType: string = withCharset(MediaType.PLAIN_TEXT, UTF8_CHARSET);\n\n /** Converts current state to ResponseInit for constructing a Response. */\n protected get responseInit(): ResponseInit {\n return {\n headers: this.headers,\n status: this.status,\n statusText: this.statusText ?? getReasonPhrase(this.status),\n webSocket: this.webSocket,\n encodeBody: \"automatic\",\n };\n }\n\n /** Sets a header, overwriting any existing value. */\n public setHeader(key: string, value: string | string[]): void {\n setHeader(this.headers, key, value);\n }\n\n /** Merges a header with existing values (does not overwrite). */\n public mergeHeader(key: string, value: string | string[]): void {\n mergeHeader(this.headers, key, value);\n }\n\n /** Adds a Content-Type header if not already existing (does not overwrite). */\n public addContentType() {\n if (!this.headers.get(HttpHeader.CONTENT_TYPE)) {\n this.setHeader(HttpHeader.CONTENT_TYPE, this.mediaType);\n }\n }\n\n /**\n * Removes headers that are disallowed or discouraged based on the current\n * status code.\n *\n * - **204 No Content:** strips headers that \"should not\" be sent\n * (`Content-Length`, `Content-Range`), per the HTTP spec.\n * - **304 Not Modified:** strips headers that \"must not\" be sent\n * (`Content-Type`, `Content-Length`, `Content-Range`, etc.), per the HTTP spec.\n *\n * This ensures that responses remain compliant with HTTP/1.1 standards while preserving\n * custom headers that are allowed.\n */\n public filterHeaders(): void {\n if (this.status === StatusCodes.NO_CONTENT) {\n filterHeaders(this.headers, FORBIDDEN_204_HEADERS);\n } else if (this.status === StatusCodes.NOT_MODIFIED) {\n filterHeaders(this.headers, FORBIDDEN_304_HEADERS);\n }\n }\n}\n\n/**\n * Base response class that adds caching headers.\n */\nabstract class CacheResponse extends BaseResponse {\n constructor(public cache?: CacheControl) {\n super();\n }\n\n /** Adds Cache-Control header if caching is configured. */\n protected addCacheHeader(): void {\n if (this.cache) {\n this.setHeader(HttpHeader.CACHE_CONTROL, CacheControl.stringify(this.cache));\n }\n }\n}\n\n/**\n * Core response. Combines caching, and content type headers.\n */\nexport abstract class WorkerResponse extends CacheResponse {\n constructor(\n private readonly body: BodyInit | null = null,\n cache?: CacheControl,\n ) {\n super(cache);\n }\n\n /** Builds the Response with body, headers, and status. */\n public async response(): Promise<Response> {\n this.addCacheHeader();\n\n const body = [StatusCodes.NO_CONTENT, StatusCodes.NOT_MODIFIED].includes(this.status)\n ? null\n : this.body;\n\n if (body) this.addContentType();\n\n this.filterHeaders();\n\n return new Response(body, this.responseInit);\n }\n}\n\n/**\n * Copies an existing response for mutation. Pass in a CacheControl\n * to be used for the response, overriding any existing `cache-control`\n * on the source response.\n */\nexport class CopyResponse extends WorkerResponse {\n constructor(response: Response, cache?: CacheControl) {\n super(response.body, cache);\n this.status = response.status;\n this.statusText = response.statusText;\n this.headers = new Headers(response.headers);\n }\n}\n\n/**\n * Copies the response, but with null body and status 304 Not Modified.\n */\nexport class NotModified extends WorkerResponse {\n constructor(response: Response) {\n super();\n this.status = StatusCodes.NOT_MODIFIED;\n this.headers = new Headers(response.headers);\n }\n}\n\n/**\n * Represents a successful response with customizable body, cache and status.\n */\nexport class SuccessResponse extends WorkerResponse {\n constructor(\n body: BodyInit | null = null,\n cache?: CacheControl,\n status: StatusCodes = StatusCodes.OK,\n ) {\n super(body, cache);\n this.status = status;\n }\n}\n\n/**\n * JSON response. Automatically sets Content-Type to application/json.\n */\nexport class JsonResponse extends SuccessResponse {\n constructor(json: unknown = {}, cache?: CacheControl, status: StatusCodes = StatusCodes.OK) {\n super(JSON.stringify(json), cache, status);\n this.mediaType = withCharset(MediaType.JSON, UTF8_CHARSET);\n }\n}\n\n/**\n * HTML response. Automatically sets Content-Type to text/html.\n */\nexport class HtmlResponse extends SuccessResponse {\n constructor(\n body: string,\n cache?: CacheControl,\n status: StatusCodes = StatusCodes.OK,\n charset: string = UTF8_CHARSET,\n ) {\n super(body, cache, status);\n this.mediaType = withCharset(MediaType.HTML, charset);\n }\n}\n\n/**\n * Plain text response. Automatically sets Content-Type to text/plain.\n */\nexport class TextResponse extends SuccessResponse {\n constructor(\n body: string,\n cache?: CacheControl,\n status: StatusCodes = StatusCodes.OK,\n charset: string = UTF8_CHARSET,\n ) {\n super(body, cache, status);\n this.mediaType = withCharset(MediaType.PLAIN_TEXT, charset);\n }\n}\n\n/**\n * Represents an HTTP response for serving binary data as `application/octet-stream`.\n *\n * This class wraps a `ReadableStream` and sets all necessary headers for both\n * full and partial content responses, handling range requests in a hybrid way\n * to maximize browser and CDN caching.\n *\n * Key behaviors:\n * - `Content-Type` is set to `application/octet-stream`.\n * - `Accept-Ranges: bytes` is always included.\n * - `Content-Length` is always set to the validated length of the response body.\n * - If the request is a true partial range (offset > 0 or length < size), the response\n * will be `206 Partial Content` with the appropriate `Content-Range` header.\n * - If the requested range covers the entire file (even if a Range header is present),\n * the response will return `200 OK` to enable browser and edge caching.\n * - Zero-length streams (`size = 0`) are never treated as partial.\n * - Special case: a requested range of `0-0` on a non-empty file is normalized to 1 byte.\n */\nexport class OctetStream extends WorkerResponse {\n constructor(stream: ReadableStream, init: OctetStreamInit, cache?: CacheControl) {\n assertOctetStreamInit(init);\n\n super(stream, cache);\n this.mediaType = MediaType.OCTET_STREAM;\n\n const normalized = OctetStream.normalizeInit(init);\n const { size, offset, length } = normalized;\n\n if (OctetStream.isPartial(normalized)) {\n this.setHeader(\n HttpHeader.CONTENT_RANGE,\n `bytes ${offset}-${offset + length - 1}/${size}`,\n );\n this.status = StatusCodes.PARTIAL_CONTENT;\n }\n\n this.setHeader(HttpHeader.ACCEPT_RANGES, \"bytes\");\n this.setHeader(HttpHeader.CONTENT_LENGTH, `${length}`);\n }\n\n /**\n * Normalizes a partially-specified `OctetStreamInit` into a fully-specified object.\n *\n * Ensures that all required fields (`size`, `offset`, `length`) are defined:\n * - `offset` defaults to 0 if not provided.\n * - `length` defaults to `size - offset` if not provided.\n * - Special case: if `offset` and `length` are both 0 but `size > 0`, `length` is set to 1\n * to avoid zero-length partial streams.\n *\n * @param init - The initial `OctetStreamInit` object, possibly with missing `offset` or `length`.\n * @returns A fully-specified `OctetStreamInit` object with `size`, `offset`, and `length` guaranteed.\n */\n private static normalizeInit(init: OctetStreamInit): Required<OctetStreamInit> {\n const { size } = init;\n const offset = init.offset ?? 0;\n let length = init.length ?? size - offset;\n\n if (offset === 0 && length === 0 && size > 0) {\n length = 1;\n }\n\n return { size, offset, length };\n }\n\n /**\n * Determines whether the given `OctetStreamInit` represents a partial range.\n *\n * Partial ranges are defined as any range that does **not** cover the entire file:\n * - If `size === 0`, the stream is never partial.\n * - If `offset === 0` and `length === size`, the stream is treated as a full file (not partial),\n * even if a Range header is present. This enables browser and CDN caching.\n * - All other cases are considered partial, and will result in a `206 Partial Content` response.\n *\n * @param init - A fully-normalized `OctetStreamInit` object.\n * @returns `true` if the stream represents a partial range; `false` if it represents the full file.\n */\n private static isPartial(init: Required<OctetStreamInit>): boolean {\n if (init.size === 0) return false;\n return !(init.offset === 0 && init.length === init.size);\n }\n}\n\n/**\n * A streaming response for Cloudflare R2 objects.\n *\n * **Partial content support:** To enable HTTP 206 streaming, you must provide\n * request headers containing the `Range` header when calling the R2 bucket's `get()` method.\n *\n * Example:\n * ```ts\n * const stream = await this.env.R2_BUCKET.get(\"key\", { range: this.request.headers });\n * ```\n *\n * @param source - The R2 object to stream.\n * @param cache - Optional caching override.\n */\nexport class R2ObjectStream extends OctetStream {\n constructor(source: R2ObjectBody, cache?: CacheControl) {\n let useCache = cache;\n if (!useCache && source.httpMetadata?.cacheControl) {\n useCache = CacheControl.parse(source.httpMetadata.cacheControl);\n }\n\n super(source.body, R2ObjectStream.computeRange(source.size, source.range), useCache);\n\n this.setHeader(HttpHeader.ETAG, source.httpEtag);\n\n if (source.httpMetadata?.contentType) {\n this.mediaType = source.httpMetadata.contentType;\n }\n }\n\n /**\n * Computes an `OctetStreamInit` object from a given R2 range.\n *\n * This function normalizes a Cloudflare R2 `R2Range` into the shape expected\n * by `OctetStream`. It handles the following cases:\n *\n * - No range provided: returns `{ size }` (full content).\n * - `suffix` range: calculates the offset and length from the end of the file.\n * - Explicit `offset` and/or `length`: passed through as-is.\n *\n * @param size - The total size of the file/object.\n * @param range - Optional range to extract (from R2). Can be:\n * - `{ offset: number; length?: number }`\n * - `{ offset?: number; length: number }`\n * - `{ suffix: number }`\n * @returns An `OctetStreamInit` object suitable for `OctetStream`.\n */\n private static computeRange(size: number, range?: R2Range): OctetStreamInit {\n if (!range) return { size };\n\n if (\"suffix\" in range) {\n const offset = Math.max(0, size - range.suffix);\n const length = size - offset;\n return { size, offset, length };\n }\n\n return { size, ...range };\n }\n}\n\n/**\n * Response for WebSocket upgrade requests.\n * Automatically sets status to 101 and attaches the client socket.\n */\nexport class WebSocketUpgrade extends WorkerResponse {\n constructor(client: WebSocket) {\n super();\n this.status = StatusCodes.SWITCHING_PROTOCOLS;\n this.webSocket = client;\n }\n}\n\n/**\n * Response for `HEAD` requests. Copy headers and status from a `GET` response\n * without the body.\n */\nexport class Head extends WorkerResponse {\n constructor(get: Response) {\n super();\n this.status = get.status;\n this.statusText = get.statusText;\n this.headers = new Headers(get.headers);\n }\n}\n\n/**\n * Response for `OPTIONS` requests.\n */\nexport class Options extends WorkerResponse {\n constructor(worker: Worker) {\n const allowed = Array.from(new Set([GET, HEAD, ...worker.getAllowedMethods()]));\n assertMethods(allowed);\n\n super();\n this.status = StatusCodes.NO_CONTENT;\n this.setHeader(HttpHeader.ALLOW, allowed);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport const UTF8_CHARSET = \"utf-8\";\n\n/**\n * Internal media types.\n */\nexport enum MediaType {\n PLAIN_TEXT = \"text/plain\",\n HTML = \"text/html\",\n JSON = \"application/json\",\n OCTET_STREAM = \"application/octet-stream\",\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OctetStreamInit } from \"../interfaces\";\n\nimport { isNumber } from \"./basic\";\n\n/**\n * Asserts that a given value is a valid `OctetStreamInit` object.\n *\n * Properties:\n * - `size` (required): must be a non-negative number.\n * - `offset` (optional): if provided, must be a number between 0 and `size`.\n * - `length` (optional): if provided, must be a non-negative number such that `offset + length <= size`.\n *\n * If `offset` or `length` are `undefined`, they are considered as `0` and `size` respectively.\n *\n * Throws an error if validation fails.\n *\n * Acts as a TypeScript type predicate, so after calling it, `value` is narrowed to `OctetStreamInit`.\n *\n * @param value - The value to validate as `OctetStreamInit`.\n * @throws TypeError If the value is not a non-null object.\n * @throws RangeError If `size`, `offset`, or `length` are invalid.\n */\nexport function assertOctetStreamInit(value: unknown): asserts value is OctetStreamInit {\n if (typeof value !== \"object\" || value === null) {\n throw new TypeError(\"OctetStreamInit must be an object.\");\n }\n\n const obj = value as Record<string, unknown>;\n\n // size\n const size = obj[\"size\"];\n if (!isNumber(size) || size < 0 || !Number.isInteger(size)) {\n throw new RangeError(\n `OctetStreamInit.size must be a non-negative integer (size=${JSON.stringify(size)}).`,\n );\n }\n\n // offset\n const offset = obj[\"offset\"] ?? 0;\n if (!isNumber(offset) || offset < 0 || offset > size || !Number.isInteger(offset)) {\n throw new RangeError(\n `OctetStreamInit.offset must be a non-negative integer less than or equal to size (size=${JSON.stringify(size)}, offset=${JSON.stringify(offset)}).`,\n );\n }\n\n // length\n const length = obj[\"length\"] ?? size - offset;\n if (!isNumber(length) || length < 0 || offset + length > size || !Number.isInteger(length)) {\n throw new RangeError(\n `OctetStreamInit.length must be a non-negative integer less than or equal to size - offset (size=${JSON.stringify(size)}, offset=${JSON.stringify(offset)}, length=${JSON.stringify(length)}).`,\n );\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Appends a charset parameter to a given media type string,\n * avoiding duplicates and ignoring empty charsets.\n *\n * @param {string} mediaType - The MIME type (e.g., \"text/html\").\n * @param {string} charset - The character set to append (e.g., \"utf-8\").\n * @returns {string} The media type with charset appended if provided.\n */\nexport function withCharset(mediaType: string, charset: string): string {\n if (!charset || mediaType.toLowerCase().includes(\"charset=\")) {\n return mediaType;\n }\n return `${mediaType}; charset=${charset.toLowerCase()}`;\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StatusCodes } from \"http-status-codes\";\n\nimport { Worker } from \"../../../interfaces\";\n\nimport { CacheRule, CacheValidators } from \"./interfaces\";\nimport { getCacheValidators } from \"./utils\";\n\n/**\n * Base class for cache validation rules.\n *\n * `ValidationRule` provides a standard mechanism for inspecting an outgoing response,\n * extracting a specific header (such as `ETag` or `Last-Modified`),\n * and applying cache validators from the incoming request.\n *\n * Subclasses implement:\n * - `getHeader()` to extract the relevant header from the response.\n * - `response()` to decide whether to return the response as-is,\n * replace it with a conditional response (e.g., `304 Not Modified`),\n * or return `undefined` to indicate the cached response is invalid.\n *\n * Rules derived from this class are typically used to implement\n * conditional GET handling and other validation-aware caching logic.\n *\n * @template H - The type of header value extracted from the response (e.g., `string` or `Date`).\n */\nexport abstract class ValidationRule<H> implements CacheRule {\n /**\n * Extracts the target header value from a response.\n *\n * Implementations should return `undefined` if the header is missing or invalid.\n *\n * @param response - The response to inspect.\n * @returns The parsed header value, or `undefined` if unavailable.\n */\n protected abstract getHeader(response: Response): H | undefined;\n\n /**\n * Applies cache validation logic using the extracted header and request validators.\n *\n * Implementations determine whether the response is still valid or requires revalidation.\n * Returning `undefined` signals that the cached response cannot be used.\n *\n * @param response - The original response from the cache or origin.\n * @param header - The extracted header value relevant to validation.\n * @param validators - Parsed conditional headers from the incoming request.\n * @returns A `Response` if valid, or `undefined` if the cache entry is invalid.\n */\n protected abstract response(\n response: Response,\n header: H,\n validators: CacheValidators,\n ): Promise<Response | undefined>;\n\n /**\n * Core entry point for cache validation rules.\n *\n * Executes the next handler in the chain, inspects the resulting response,\n * and applies subclass-specific validation logic if appropriate.\n *\n * @param worker - The worker context for the current request.\n * @param next - A function that invokes the next rule or final handler.\n * @returns A validated `Response`, or `undefined` if validation fails.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n const response = await next();\n if (!response || response.status !== StatusCodes.OK) return response;\n\n const header = this.getHeader(response);\n if (header === undefined) return response;\n\n const validators = getCacheValidators(worker.request.headers);\n return this.response(response, header, validators);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../../constants/headers\";\nimport { PreconditionFailed } from \"../../../errors\";\nimport { NotModified } from \"../../../responses\";\n\nimport { CacheValidators } from \"./interfaces\";\nimport { isNotModified, isPreconditionFailed } from \"./utils\";\nimport { ValidationRule } from \"./validation\";\n\n/**\n * Base class for ETag-based cache validation rules.\n *\n * `MatchRule` specializes `ValidationRule` to handle `ETag` headers.\n * It extracts the `ETag` from the response and delegates validation\n * to subclasses via the `response()` method.\n *\n * Subclasses implement the behavior for specific conditional requests,\n * such as `If-Match` or `If-None-Match`.\n */\nabstract class MatchRule extends ValidationRule<string> {\n /**\n * Extracts the `ETag` header from a response.\n *\n * @param response - The response to inspect.\n * @returns The ETag string if present; otherwise `undefined`.\n */\n protected override getHeader(response: Response): string | undefined {\n return response.headers.get(HttpHeader.ETAG) ?? undefined;\n }\n}\n\n/**\n * Implements the `If-Match` conditional request validation.\n *\n * If the `If-Match` header is present and the response’s ETag does\n * not match any of the listed values, the rule returns a\n * `412 Precondition Failed` response.\n *\n * Otherwise, the original response is returned unchanged.\n */\nexport class IfMatchRule extends MatchRule {\n /**\n * Applies `If-Match` validation against the response’s ETag.\n *\n * @param response - The original response from cache.\n * @param etag - The ETag extracted from the response.\n * @param validators - Parsed cache validators from the request.\n * @returns A `Response` with `412 Precondition Failed` if validation fails,\n * or the original response if the precondition passes.\n */\n protected async response(\n response: Response,\n etag: string,\n validators: CacheValidators,\n ): Promise<Response | undefined> {\n if (isPreconditionFailed(validators.ifMatch, etag)) {\n return new PreconditionFailed(`ETag: ${etag}`).response();\n }\n\n return response;\n }\n}\n\n/**\n * Implements the `If-None-Match` conditional request validation.\n *\n * If the `If-None-Match` header is present and the response’s ETag matches\n * one of the listed values, the rule returns a `304 Not Modified` response.\n *\n * If `If-None-Match` is present but does not match, the cache entry\n * is considered invalid and `undefined` is returned.\n */\nexport class IfNoneMatchRule extends MatchRule {\n /**\n * Applies `If-None-Match` validation against the response’s ETag.\n *\n * @param response - The original response from cache.\n * @param etag - The ETag extracted from the response.\n * @param validators - Parsed cache validators from the request.\n * @returns A `304 Not Modified` response if the resource is unmodified,\n * the original response if no validators are present,\n * or `undefined` if validation fails and the cache should not be used.\n */\n protected async response(\n response: Response,\n etag: string,\n validators: CacheValidators,\n ): Promise<Response | undefined> {\n if (validators.ifNoneMatch.length === 0) return response;\n\n if (isNotModified(validators.ifNoneMatch, etag)) {\n return new NotModified(response).response();\n }\n\n return undefined;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GET, HEAD } from \"../../../constants\";\nimport { Worker } from \"../../../interfaces\";\nimport { Head } from \"../../../responses\";\n\nimport { CacheRule } from \"./interfaces\";\n\n/**\n * Determines cache eligibility based on the HTTP method.\n *\n * - `GET` requests pass the cached response to the next rule in the chain.\n * - `HEAD` requests convert a cached `GET` response into a `HEAD` response\n * before passing it along.\n * - All other methods bypass the cache and return `undefined`.\n */\nexport class MethodRule implements CacheRule {\n /**\n * Applies method-based cache validation.\n *\n * @param worker - The worker context containing the request.\n * @param next - Function invoking the next cache rule or returning a cached response.\n * @returns The cached response if eligible, a transformed `HEAD` response,\n * or `undefined` if the cache cannot be used.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n if (worker.request.method === GET) {\n return next();\n }\n\n if (worker.request.method === HEAD) {\n const response = await next();\n if (!response) return undefined;\n\n return new Head(response).response();\n }\n\n return undefined;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../../constants/headers\";\nimport { PreconditionFailed } from \"../../../errors\";\nimport { NotModified } from \"../../../responses\";\n\nimport { CacheValidators } from \"./interfaces\";\nimport { toDate } from \"./utils\";\nimport { ValidationRule } from \"./validation\";\n\n/**\n * Base class for `Last-Modified` header cache validation rules.\n *\n * `LastModifiedRule` specializes `ValidationRule` to handle the\n * `Last-Modified` header. It converts the header value into a\n * timestamp and delegates validation logic to subclasses.\n *\n * Subclasses implement behavior for conditional requests such as\n * `If-Modified-Since` and `If-Unmodified-Since`.\n */\nabstract class LastModifiedRule extends ValidationRule<number> {\n /**\n * Extracts and parses the `Last-Modified` header from a response.\n *\n * @param response - The response to inspect.\n * @returns The timestamp in milliseconds since epoch, or `undefined` if unavailable.\n */\n protected override getHeader(response: Response): number | undefined {\n return toDate(response.headers.get(HttpHeader.LAST_MODIFIED));\n }\n}\n\n/**\n * Implements the `If-Modified-Since` conditional request validation.\n *\n * If the resource has not been modified since the specified timestamp,\n * the rule returns a `304 Not Modified` response.\n *\n * Otherwise, `undefined` is returned to indicate the cache entry\n * cannot be used.\n */\nexport class ModifiedSinceRule extends LastModifiedRule {\n /**\n * Applies `If-Modified-Since` validation against the response’s `Last-Modified` value.\n *\n * @param response - The original response from cache.\n * @param lastModified - Timestamp of the resource’s last modification.\n * @param validators - Parsed cache validators from the request.\n * @returns A `304 Not Modified` response if the resource is unmodified,\n * the original response if no validator is present,\n * or `undefined` if the cache should not be used.\n */\n protected async response(\n response: Response,\n lastModified: number,\n validators: CacheValidators,\n ): Promise<Response | undefined> {\n const modifiedSince = toDate(validators.ifModifiedSince);\n if (modifiedSince === undefined) return response;\n\n if (lastModified <= modifiedSince) return new NotModified(response).response();\n\n return undefined;\n }\n}\n\n/**\n * Implements the `If-Unmodified-Since` conditional request validation.\n *\n * If the resource has been modified after the specified timestamp,\n * the rule returns a `412 Precondition Failed` response.\n *\n * Otherwise, the original response is returned.\n */\nexport class UnmodifiedSinceRule extends LastModifiedRule {\n /**\n * Applies `If-Unmodified-Since` validation against the response’s `Last-Modified` value.\n *\n * @param response - The original response from cache.\n * @param lastModified - Timestamp of the resource’s last modification.\n * @param validators - Parsed cache validators from the request.\n * @returns A `412 Precondition Failed` response if the resource was modified\n * after the specified timestamp, or the original response if valid.\n */\n protected async response(\n response: Response,\n lastModified: number,\n validators: CacheValidators,\n ): Promise<Response | undefined> {\n const unmodifiedSince = toDate(validators.ifUnmodifiedSince);\n if (unmodifiedSince === undefined) return response;\n\n if (lastModified > unmodifiedSince) {\n return new PreconditionFailed(\n `Last-Modified: ${new Date(lastModified).toUTCString()}`,\n ).response();\n }\n\n return response;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StatusCodes } from \"http-status-codes\";\n\nimport { Worker } from \"../../../interfaces/worker\";\n\nimport { CacheRule } from \"./interfaces\";\nimport { getContentLength, getRange } from \"./utils\";\n\n/**\n * Ensures cached responses can satisfy requests with a `Range` header.\n *\n * - Only full or full-from-start ranges are eligible.\n * - Requests with non-zero `start`, zero `end`, or mismatched `end` values\n * bypass the cache (`undefined` is returned).\n * - Requests without a `Range` header, or with open-ended ranges from 0,\n * pass the cached response to the next rule in the chain.\n */\nexport class RangeRule implements CacheRule {\n /**\n * Applies range-based cache validation.\n *\n * @param worker - The worker context containing the request.\n * @param next - Function invoking the next cache rule or returning a cached response.\n * @returns The cached response if it satisfies the requested range,\n * or `undefined` if the cache cannot be used.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n const range = getRange(worker.request);\n\n if (range && (range.start !== 0 || range.end === 0)) {\n return undefined;\n }\n\n const response = await next();\n if (!response || response.status !== StatusCodes.OK) return response;\n\n if (!range) return response;\n if (range.end === undefined) return response;\n\n const length = getContentLength(response.headers);\n if (!length) return undefined;\n if (range.end !== length - 1) return undefined;\n\n return response;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../../constants/headers\";\nimport { Worker } from \"../../../interfaces\";\n\nimport { CacheRule } from \"./interfaces\";\n\n/**\n * Prevents using the cache for requests that contain sensitive headers.\n *\n * - Requests with `Authorization` or `Cookie` headers bypass the cache.\n * - Otherwise, the request passes to the next rule in the chain.\n */\nexport class SecurityRule implements CacheRule {\n /**\n * Applies security-based cache validation.\n *\n * @param worker - The worker context containing the request.\n * @param next - Function invoking the next cache rule or returning a cached response.\n * @returns The cached response if allowed, or `undefined` if the cache cannot be used.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n const headers = worker.request.headers;\n if (headers.has(HttpHeader.AUTHORIZATION)) {\n return undefined;\n }\n if (headers.has(HttpHeader.COOKIE)) {\n return undefined;\n }\n\n return next();\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../../constants/headers\";\nimport { Worker } from \"../../../interfaces\";\n\nimport { CacheRule } from \"./interfaces\";\n\n/**\n * Prevents using the cache for requests that include the `Upgrade` header.\n *\n * - If the `Upgrade` header is present, the cache is bypassed.\n * - Otherwise, the request passes to the next rule in the chain.\n */\nexport class UpgradeRule implements CacheRule {\n /**\n * Applies the upgrade-header validation.\n *\n * @param worker - The worker context containing the request.\n * @param next - Function invoking the next cache rule or returning a cached response.\n * @returns The cached response if allowed, or `undefined` if the cache cannot be used.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n if (worker.request.headers.has(HttpHeader.UPGRADE)) {\n return undefined;\n }\n\n return next();\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CacheControl } from \"../../constants\";\nimport { HttpHeader } from \"../../constants/headers\";\nimport { WorkerResponse } from \"../../responses\";\nimport { getHeaderValues } from \"../../utils/headers\";\n\nimport { getCacheControl, getFilteredVary } from \"./utils\";\n\n/**\n * Represents a Vary-aware cached response.\n *\n * Extends WorkerResponse to track which request headers affect the cached\n * response (Vary headers) and ensure correct TTL handling.\n *\n * This class is used internally in the caching system to:\n * - Store responses with full awareness of Vary headers.\n * - Append new Vary headers safely.\n * - Update TTLs to match incoming origin responses.\n * - Track whether the cached variant has been modified.\n */\nexport class VariantResponse extends WorkerResponse {\n private _isModified = false;\n\n private constructor(vary: string[]) {\n const filtered = getFilteredVary(vary);\n if (filtered.length === 0) {\n throw new Error(\"The filtered vary array is empty.\");\n }\n\n super();\n this.setHeader(HttpHeader.INTERNAL_VARIANT_SET, filtered);\n }\n\n /**\n * Creates a new VariantResponse with the specified Vary headers.\n *\n * @param vary - Array of request headers this response varies on.\n * @returns A new VariantResponse instance.\n */\n public static new(vary: string[]): VariantResponse {\n return new VariantResponse(getFilteredVary(vary));\n }\n\n /**\n * Restores a VariantResponse from an existing Response.\n *\n * @param source - The cached Response to restore.\n * @throws If the source response is not a variant response.\n * @returns A VariantResponse instance containing the original Vary headers and cache control.\n */\n public static restore(source: Response): VariantResponse {\n if (!VariantResponse.isVariantResponse(source)) {\n throw new Error(\"The source response is not a variant response\");\n }\n\n const variant = VariantResponse.new(\n getHeaderValues(source.headers, HttpHeader.INTERNAL_VARIANT_SET),\n );\n\n const cacheControl = source.headers.get(HttpHeader.CACHE_CONTROL);\n if (cacheControl) variant.cache = CacheControl.parse(cacheControl);\n\n return variant;\n }\n\n /**\n * Returns the Vary headers tracked by this response.\n */\n public get vary(): string[] {\n return getHeaderValues(this.headers, HttpHeader.INTERNAL_VARIANT_SET);\n }\n\n /**\n * Indicates whether the variant has been modified since creation or restoration.\n */\n public get isModified(): boolean {\n return this._isModified;\n }\n\n /**\n * Appends additional Vary headers to this response.\n *\n * Updates the internal _isModified flag if new headers are added.\n *\n * @param vary - Array of headers to merge into the existing Vary set.\n */\n public append(vary: string[]): void {\n const before = this.vary.length;\n this.mergeHeader(HttpHeader.INTERNAL_VARIANT_SET, getFilteredVary(vary));\n this._isModified = this.vary.length !== before;\n }\n\n /**\n * Determines if a response is a VariantResponse.\n *\n * @param response - The Response object to inspect.\n * @returns `true` if the response is a variant; otherwise `false`.\n */\n public static isVariantResponse(response: Response): boolean {\n return response.headers.has(HttpHeader.INTERNAL_VARIANT_SET);\n }\n\n /**\n * Updates this variant’s TTL to ensure it does not expire before\n * the TTL of the given origin response.\n *\n * Only modifies the TTL if the origin response explicitly provides\n * s-maxage or max-age. Updates _isModified if the TTL increases.\n *\n * @param response - The origin Response whose TTL should be considered.\n */\n public expireAfter(response: Response): void {\n const incoming = getCacheControl(response.headers);\n\n const incomingTTL = incoming[\"s-maxage\"] ?? incoming[\"max-age\"];\n if (incomingTTL === undefined) return;\n\n const currentTTL = this.cache?.[\"s-maxage\"];\n\n if (currentTTL === undefined || incomingTTL > currentTTL) {\n this.cache = {\n \"s-maxage\": incomingTTL,\n };\n this._isModified = true;\n }\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { assertKey } from \"../../guards/cache\";\nimport { CacheInit } from \"../../interfaces/cache\";\nimport { Middleware } from \"../../interfaces/middleware\";\nimport { Worker } from \"../../interfaces/worker\";\n\nimport { sortSearchParams } from \"./keys\";\nimport { CachePolicy } from \"./policy\";\nimport { CacheControlRule } from \"./rules/control\";\nimport { IfMatchRule, IfNoneMatchRule } from \"./rules/etag\";\nimport { MethodRule } from \"./rules/method\";\nimport { ModifiedSinceRule, UnmodifiedSinceRule } from \"./rules/modified\";\nimport { RangeRule } from \"./rules/range\";\nimport { SecurityRule } from \"./rules/security\";\nimport { UpgradeRule } from \"./rules/upgrade\";\nimport { getVaryHeader, getVaryKey, isCacheable } from \"./utils\";\nimport { VariantResponse } from \"./variant\";\n\n/**\n * Cache Middleware Implementation\n */\nexport class CacheHandler implements Middleware {\n private readonly init: CacheInit;\n\n constructor(init: CacheInit) {\n const { name, getKey = sortSearchParams } = init;\n\n this.init = {\n name: name?.trim() || undefined,\n getKey,\n };\n }\n\n /**\n * Handles an incoming request through the cache middleware.\n *\n * Behavior:\n * - Opens the configured cache (or the default cache if none specified).\n * - Creates a `CachePolicy` with default rules (GET check, range check, ETag handling).\n * - Executes the policy to determine if a cached response can be used.\n * - If a cached response is found and valid per the rules, it is returned.\n * - If no cached response is usable, the `next()` handler is invoked to fetch a fresh response.\n * - Stores the fresh response in the cache if it is cacheable.\n *\n * Note: The cache policy only checks if an existing cached response is usable.\n * It does not store the response; storage is handled later in `setCached()`.\n *\n * @param worker - The Worker instance containing the request and context.\n * @param next - Function to invoke the next middleware or origin fetch.\n * @returns A `Response` object, either from cache or freshly fetched.\n */\n public async handle(worker: Worker, next: () => Promise<Response>): Promise<Response> {\n const cache = this.init.name ? await caches.open(this.init.name) : caches.default;\n\n const policy = new CachePolicy()\n .use(new CacheControlRule())\n .use(new MethodRule())\n .use(new UpgradeRule())\n .use(new SecurityRule())\n .use(new RangeRule())\n .use(new ModifiedSinceRule())\n .use(new IfNoneMatchRule())\n .use(new UnmodifiedSinceRule())\n .use(new IfMatchRule());\n\n const cacheResponse = await policy.execute(worker, () =>\n this.getCached(cache, worker.request),\n );\n if (cacheResponse) return cacheResponse;\n\n const response = await next();\n\n worker.ctx.waitUntil(this.setCached(cache, worker.request, response));\n return response;\n }\n\n /**\n * Attempts to retrieve a cached response for the given request.\n *\n * Checks the base cache key first. If the cached response is a `VariantResponse`,\n * it computes the variant-specific key using the `Vary` headers and returns\n * the corresponding cached response. Otherwise, returns the base cached response.\n *\n * Returns `undefined` if no cached response exists or if the cached response\n * fails validation (e.g., rules in `CachePolicy` would prevent it from being used).\n *\n * @param cache - The Cache to query.\n * @param request - The Request for which to retrieve a cached response.\n * @returns A Promise resolving to the cached Response if found and usable, or `undefined`.\n */\n public async getCached(cache: Cache, request: Request): Promise<Response | undefined> {\n const key = this.getCacheKey(request);\n\n const response = await cache.match(key.toString());\n if (!response) return undefined;\n if (!VariantResponse.isVariantResponse(response)) return response;\n\n const vary = VariantResponse.restore(response).vary;\n const varyKey = getVaryKey(request, vary, key);\n return cache.match(varyKey);\n }\n\n /**\n * Stores a response in the cache for the given request, handling `Vary` headers\n * and response variants.\n *\n * The method follows these rules:\n * 1. If the response is not cacheable (per `isCacheable`), it returns immediately.\n * 2. If no cached entry exists:\n * - If the response has no `Vary` headers, the response is cached directly.\n * - If there are `Vary` headers, a `VariantResponse` is created to track\n * which headers affect caching, and both the variant placeholder and the\n * actual response are stored.\n * 3. If a cached entry exists and is a `VariantResponse`:\n * - The `Vary` headers are merged into the variant record.\n * - The variant-specific response is updated in the cache.\n * - TTL is updated to match the most permissive TTL from the origin response.\n * 4. If a cached entry exists but is not a variant and the new response has `Vary` headers:\n * - The cached non-variant is converted into a `VariantResponse`.\n * - Both the new response and the original cached response are stored under appropriate variant keys.\n *\n * @param cache - The Cache where the response should be stored.\n * @param worker - The Worker instance containing the request and execution context.\n * @param response - The Response to cache.\n */\n public async setCached(cache: Cache, request: Request, response: Response): Promise<void> {\n if (!isCacheable(request, response)) return;\n\n const key = this.getCacheKey(request);\n const clone = response.clone();\n const vary = getVaryHeader(clone);\n const cached = await cache.match(key);\n const isCachedVariant = cached && VariantResponse.isVariantResponse(cached);\n\n if (!cached) {\n if (vary.length === 0) {\n await cache.put(key, clone);\n return;\n }\n\n const variantResponse = VariantResponse.new(vary);\n variantResponse.expireAfter(clone);\n await cache.put(key, await variantResponse.response());\n await cache.put(getVaryKey(request, variantResponse.vary, key), clone);\n return;\n }\n\n if (isCachedVariant) {\n const variantResponse = VariantResponse.restore(cached);\n variantResponse.expireAfter(clone);\n if (vary.length > 0) {\n variantResponse.append(vary);\n if (variantResponse.isModified) {\n await cache.put(key, await variantResponse.response());\n }\n }\n await cache.put(getVaryKey(request, variantResponse.vary, key), clone);\n return;\n }\n\n if (vary.length === 0) {\n await cache.put(key, clone);\n return;\n }\n\n // We have an existing cache entry that is non-variant, but the response\n // being processed has a vary header. Create and cache a new variant\n // response that replaces the cached non-variant response. Then save both\n // the new response and the cached response with generated variant keys.\n const variantResponse = VariantResponse.new(vary);\n variantResponse.expireAfter(cached);\n variantResponse.expireAfter(clone);\n await cache.put(key, await variantResponse.response());\n await cache.put(getVaryKey(request, variantResponse.vary, key), clone);\n await cache.put(getVaryKey(request, [], key), cached);\n }\n\n /**\n * Returns the cache key for a request.\n *\n * By default, this is a normalized URL including the path and query string.\n * However, users can provide a custom `getKey` function when creating the\n * `cache` middleware to fully control how the cache keys are generated.\n *\n * For example, a custom function could:\n * - Sort or remove query parameters\n * - Exclude the search/query string entirely\n * - Modify the path or host\n *\n * This allows complete flexibility over cache key generation.\n *\n * @param request The Request object to generate a cache key for.\n * @returns A URL representing the main cache key for this request.\n */\n public getCacheKey(request: Request): URL {\n const key = this.init.getKey(request);\n assertKey(key);\n\n key.hash = \"\";\n return key;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { assertCacheInit } from \"../../guards/cache\";\nimport { CacheInit } from \"../../interfaces/cache\";\nimport { Middleware } from \"../../interfaces/middleware\";\n\nimport { CacheHandler } from \"./handler\";\n\n/**\n * Creates a Vary-aware caching middleware for Workers.\n *\n * This middleware:\n * - Caches `GET` requests **only**.\n * - Respects the `Vary` header of responses, ensuring that requests\n * with different headers (e.g., `Accept-Language`) receive the correct cached response.\n * - Skips caching for non-cacheable responses (e.g., error responses or\n * responses with `Vary: *`).\n *\n * @param init Optional cache configuration object.\n * @param init.name Optional name of the cache to use. If omitted, the default cache is used.\n * @param init.getKey Optional function to compute a custom cache key from a request.\n *\n * @returns A {@link Middleware} instance that can be used in your middleware chain.\n */\nexport function cache(init: Partial<CacheInit> = {}): Middleware {\n assertCacheInit(init);\n\n return new CacheHandler(init);\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CorsInit } from \"../interfaces/cors\";\n\nimport { isBoolean, isNumber, isStringArray } from \"./basic\";\n\n/**\n * Throws if the given value is not a valid CorsInit.\n *\n * Checks only the fields that are present, since CorsInit is Partial<CorsConfig>.\n *\n * @param value - The value to check.\n * @throws TypeError If `value` is not a valid {@link CorsInit}.\n */\nexport function assertCorsInit(value: unknown): asserts value is CorsInit {\n if (value === undefined) return;\n\n if (typeof value !== \"object\" || value === null) {\n throw new TypeError(\"CorsInit must be an object.\");\n }\n\n const obj = value as Record<string, unknown>;\n\n if (obj[\"allowedOrigins\"] !== undefined && !isStringArray(obj[\"allowedOrigins\"])) {\n throw new TypeError(\"CorsInit.allowedOrigins must be a string array.\");\n }\n\n if (obj[\"allowedHeaders\"] !== undefined && !isStringArray(obj[\"allowedHeaders\"])) {\n throw new TypeError(\"CorsInit.allowedHeaders must be a string array.\");\n }\n\n if (obj[\"exposedHeaders\"] !== undefined && !isStringArray(obj[\"exposedHeaders\"])) {\n throw new TypeError(\"CorsInit.exposedHeaders must be a string array.\");\n }\n\n if (obj[\"allowCredentials\"] !== undefined && !isBoolean(obj[\"allowCredentials\"])) {\n throw new TypeError(\"CorsInit.allowCredentials must be a boolean.\");\n }\n\n if (obj[\"maxAge\"] !== undefined && !isNumber(obj[\"maxAge\"])) {\n throw new TypeError(\"CorsInit.maxAge must be a number.\");\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StatusCodes } from \"../../constants\";\nimport { HttpHeader } from \"../../constants/headers\";\nimport { Time } from \"../../constants/time\";\nimport { CorsConfig } from \"../../interfaces/cors\";\n\n/**\n * A wildcard value used in the CORS `Access-Control-Allow-Origin` header\n * to permit requests from **any** origin.\n */\nexport const ALLOW_ALL_ORIGINS = \"*\";\n\n/**\n * Status codes for which `CORS` should be skipped.\n *\n * Skips `CORS` for:\n * - 101 Switching Protocols (WebSocket upgrade)\n * - 100 Continue\n * - 3xx Redirects (`CORS` is applied to the final URL only)\n */\nexport const SKIP_CORS_STATUSES = [\n StatusCodes.SWITCHING_PROTOCOLS,\n StatusCodes.CONTINUE,\n StatusCodes.PROCESSING,\n StatusCodes.EARLY_HINTS,\n StatusCodes.MOVED_PERMANENTLY,\n StatusCodes.MOVED_TEMPORARILY,\n StatusCodes.SEE_OTHER,\n StatusCodes.TEMPORARY_REDIRECT,\n StatusCodes.PERMANENT_REDIRECT,\n];\n\n/**\n * Default configuration for `CORS` middleware.\n */\nexport const defaultCorsConfig: CorsConfig = {\n allowedOrigins: [ALLOW_ALL_ORIGINS],\n allowedHeaders: [HttpHeader.CONTENT_TYPE],\n exposedHeaders: [],\n allowCredentials: false,\n maxAge: 5 * Time.Minute,\n} as const;\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../constants/headers\";\nimport { assertMethods } from \"../../guards/methods\";\nimport { CorsConfig } from \"../../interfaces/cors\";\nimport { Worker } from \"../../interfaces/worker\";\nimport { CopyResponse } from \"../../responses\";\nimport { mergeHeader, setHeader } from \"../../utils/headers\";\n\nimport { ALLOW_ALL_ORIGINS, SKIP_CORS_STATUSES } from \"./constants\";\n\n/**\n * Handles a `CORS` preflight `OPTIONS` request.\n *\n * This function **modifies the provided response** to include the appropriate\n * CORS headers based on the configuration and the request's origin.\n *\n * Steps:\n * 1. Clears any existing CORS headers from the response.\n * 2. Sets `Vary: Origin` if needed for caching purposes.\n * 3. If an `Origin` header is present in the request:\n * - Sets `Access-Control-Allow-Origin`\n * - Sets `Access-Control-Allow-Credentials`\n * - Sets `Access-Control-Allow-Methods`\n * - Sets `Access-Control-Allow-Headers`\n * - Sets `Access-Control-Max-Age`\n *\n * @param response - The original Response object to modify for the preflight.\n * @param worker - The Worker handling the request.\n * @param cors - The CORS configuration to apply.\n * @returns A Response object suitable for responding to the preflight request.\n */\nexport function options(\n response: Response,\n worker: Worker,\n cors: CorsConfig,\n): Promise<Response> {\n const copy = new CopyResponse(response);\n const origin = getOrigin(worker.request);\n\n deleteCorsHeaders(copy.headers);\n setVaryOrigin(copy.headers, cors);\n\n if (origin) {\n setAllowOrigin(copy.headers, cors, origin);\n setAllowCredentials(copy.headers, cors, origin);\n setAllowMethods(copy.headers, worker);\n setAllowHeaders(copy.headers, cors);\n setMaxAge(copy.headers, cors);\n }\n\n return copy.response();\n}\n\n/**\n * Applies CORS headers to an existing response for non-preflight requests.\n *\n * This function **modifies the provided response** to include the appropriate\n * CORS headers based on the configuration and the request's origin.\n *\n * Steps:\n * 1. Clears any existing CORS headers from the response.\n * 2. Sets `Vary: Origin` if needed for caching purposes.\n * 3. If an `Origin` header is present in the request:\n * - Sets `Access-Control-Allow-Origin`\n * - Sets `Access-Control-Allow-Credentials`\n * - Sets `Access-Control-Expose-Headers`\n *\n * @param response - The original Response object to modify.\n * @param worker - The Worker handling the request.\n * @param cors - The CORS configuration to apply.\n * @returns A Response object with CORS headers applied, suitable for returning\n * to the client.\n */\nexport function apply(\n response: Response,\n worker: Worker,\n cors: CorsConfig,\n): Promise<Response> {\n const copy = new CopyResponse(response);\n const origin = getOrigin(worker.request);\n\n deleteCorsHeaders(copy.headers);\n setVaryOrigin(copy.headers, cors);\n\n if (origin) {\n setAllowOrigin(copy.headers, cors, origin);\n setAllowCredentials(copy.headers, cors, origin);\n setExposedHeaders(copy.headers, cors);\n }\n\n return copy.response();\n}\n\n/**\n * Adds `Vary: Origin` when `CORS` is restricted to specific origins.\n * This ensures caches differentiate responses by request origin.\n *\n * Skipped when all origins are allowed.\n *\n * @param headers - The headers object to modify.\n * @param cors - The `CORS` configuration.\n */\nexport function setVaryOrigin(headers: Headers, cors: CorsConfig): void {\n if (!allowAllOrigins(cors)) {\n mergeHeader(headers, HttpHeader.VARY, HttpHeader.ORIGIN);\n }\n}\n\n/**\n * Sets the Access-Control-Allow-Origin header based on the `CORS` config\n * and request origin.\n *\n * @param headers - The headers object to modify.\n * @param cors - The `CORS` configuration.\n * @param origin - The request's origin, or null if not present.\n */\nexport function setAllowOrigin(headers: Headers, cors: CorsConfig, origin: string): void {\n if (allowAllOrigins(cors)) {\n setHeader(headers, HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN, ALLOW_ALL_ORIGINS);\n return;\n }\n\n if (cors.allowedOrigins.includes(origin)) {\n setHeader(headers, HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN, origin);\n }\n}\n\n/**\n * Conditionally sets the `Access-Control-Allow-Credentials` header\n * for a `CORS` response.\n *\n * This header is only set if:\n * 1. `cors.allowCredentials` is true,\n * 2. The configuration does **not** allow any origin (`*`), and\n * 3. The provided `origin` is explicitly listed in `cors.allowedOrigins`.\n *\n * @param headers - The Headers object to modify.\n * @param cors - The `CORS` configuration.\n * @param origin - The origin of the incoming request.\n */\nexport function setAllowCredentials(headers: Headers, cors: CorsConfig, origin: string): void {\n if (!cors.allowCredentials) return;\n if (allowAllOrigins(cors)) return;\n if (!cors.allowedOrigins.includes(origin)) return;\n\n setHeader(headers, HttpHeader.ACCESS_CONTROL_ALLOW_CREDENTIALS, \"true\");\n}\n\n/**\n * Sets the `Access-Control-Allow-Methods` header for a `CORS` response.\n *\n * @param headers - The Headers object to modify.\n * @param worker - The Worker instance used to retrieve allowed methods.\n */\nexport function setAllowMethods(headers: Headers, worker: Worker): void {\n const allowed = worker.getAllowedMethods();\n assertMethods(allowed);\n\n setHeader(headers, HttpHeader.ACCESS_CONTROL_ALLOW_METHODS, allowed);\n}\n\n/**\n * Sets the `Access-Control-Max-Age` header for a `CORS` response.\n *\n * This header indicates how long the results of a preflight request\n * can be cached by the client (in seconds).\n *\n * The value is **clamped to a non-negative integer** to comply with\n * the `CORS` specification:\n * - Decimal values are floored to the nearest integer.\n * - Negative values are treated as `0`.\n *\n * @param headers - The Headers object to modify.\n * @param cors - The `CORS` configuration containing the `maxAge` value in seconds.\n */\nexport function setMaxAge(headers: Headers, cors: CorsConfig): void {\n const maxAge = Math.max(0, Math.floor(cors.maxAge));\n setHeader(headers, HttpHeader.ACCESS_CONTROL_MAX_AGE, String(maxAge));\n}\n\n/**\n * Sets the Access-Control-Allow-Headers header based on the `CORS` configuration.\n *\n * Only the headers explicitly listed in `cors.allowedHeaders` are sent.\n * If the array is empty, no Access-Control-Allow-Headers header is added.\n *\n * @param headers - The Headers object to modify.\n * @param cors - The `CORS` configuration.\n */\nexport function setAllowHeaders(headers: Headers, cors: CorsConfig): void {\n setHeader(headers, HttpHeader.ACCESS_CONTROL_ALLOW_HEADERS, cors.allowedHeaders);\n}\n\n/**\n * Sets the Access-Control-Expose-Headers header for a response.\n *\n * @param headers - The headers object to modify.\n * @param cors - The `CORS` configuration.\n */\nexport function setExposedHeaders(headers: Headers, cors: CorsConfig): void {\n setHeader(headers, HttpHeader.ACCESS_CONTROL_EXPOSE_HEADERS, cors.exposedHeaders);\n}\n\n/**\n * Returns true if the `CORS` config allows all origins ('*').\n *\n * @param cors - The `CORS` configuration.\n */\nexport function allowAllOrigins(cors: CorsConfig): boolean {\n return cors.allowedOrigins.includes(ALLOW_ALL_ORIGINS);\n}\n\n/**\n * Deletes any existing `CORS` headers from the provided headers object.\n *\n * @param headers - The headers object to modify.\n */\nexport function deleteCorsHeaders(headers: Headers): void {\n headers.delete(HttpHeader.ACCESS_CONTROL_MAX_AGE);\n headers.delete(HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN);\n headers.delete(HttpHeader.ACCESS_CONTROL_ALLOW_HEADERS);\n headers.delete(HttpHeader.ACCESS_CONTROL_ALLOW_METHODS);\n headers.delete(HttpHeader.ACCESS_CONTROL_EXPOSE_HEADERS);\n headers.delete(HttpHeader.ACCESS_CONTROL_ALLOW_CREDENTIALS);\n}\n\n/**\n * Determines whether CORS headers should be skipped for a response.\n *\n * Certain responses do not require CORS headers, such as:\n * - Informational or protocol-level responses (1xx)\n * - Redirect responses (3xx)\n * - Responses initiating a protocol upgrade\n *\n * @param response - The Response object to inspect.\n * @returns `true` if CORS headers should be omitted, `false` otherwise.\n */\nexport function skipCors(response: Response): boolean {\n const { status, headers } = response;\n if (SKIP_CORS_STATUSES.includes(status)) return true;\n if (headers.has(HttpHeader.UPGRADE)) return true;\n\n return false;\n}\n\n/**\n * Extracts and normalizes the `Origin` header from a request.\n *\n * Returns the origin (scheme + host + port) as a string if present and valid.\n * Returns `null` if:\n * - The `Origin` header is missing\n * - The `Origin` header is `\"null\"` (opaque origin)\n * - The `Origin` header is malformed\n *\n * @param request - The incoming {@link Request} object.\n * @returns The normalized origin string, or `null` if not present or invalid.\n */\nexport function getOrigin(request: Request): string | null {\n const origin = request.headers.get(HttpHeader.ORIGIN)?.trim();\n if (!origin || origin === \"null\") return null;\n\n try {\n return new URL(origin).origin;\n } catch {\n return null;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OPTIONS } from \"../../constants/methods\";\nimport { CorsConfig, CorsInit } from \"../../interfaces/cors\";\nimport { Middleware } from \"../../interfaces/middleware\";\nimport { Worker } from \"../../interfaces/worker\";\n\nimport { defaultCorsConfig } from \"./constants\";\nimport { apply, options, skipCors } from \"./utils\";\n\n/**\n * CORS Middleware Implementation\n *\n * Handles Cross-Origin Resource Sharing (CORS) for incoming requests.\n *\n * Behavior:\n * - Invokes the downstream middleware for all requests first by calling `next()`.\n * - If the request is an `OPTIONS` request (preflight), transforms the downstream\n * response into a preflight CORS response.\n * - For other HTTP methods, applies CORS headers to the downstream response,\n * unless the response explicitly opts out via `skipCors`.\n *\n * This ensures that all responses comply with the configured CORS rules\n * while still allowing downstream middleware to run for every request.\n *\n * @see {@link cors} for full configuration options and defaults.\n */\nexport class CorsHandler implements Middleware {\n /** The resolved CORS configuration for this middleware instance. */\n private readonly config: CorsConfig;\n\n /**\n * Constructs a new `CorsHandler` instance.\n *\n * Merges the provided partial configuration with the default settings.\n *\n * @param init - Optional partial configuration to override defaults.\n * Any fields not provided will use `defaultCorsConfig`.\n */\n constructor(init?: CorsInit) {\n this.config = { ...defaultCorsConfig, ...init };\n }\n\n /**\n * Applies CORS handling to an incoming request.\n *\n * @param worker - The Worker instance containing the request and context.\n * @param next - Function invoking the next middleware in the chain.\n * @returns A Response object with CORS headers applied.\n * - For `OPTIONS` requests, returns a preflight response based on\n * the downstream response.\n * - For other methods, returns the downstream response with\n * CORS headers applied, unless `skipCors` prevents it.\n */\n public async handle(worker: Worker, next: () => Promise<Response>): Promise<Response> {\n const response = await next();\n\n if (worker.request.method === OPTIONS) {\n return options(response, worker, this.config);\n }\n\n if (skipCors(response)) return response;\n\n return apply(response, worker, this.config);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { assertCorsInit } from \"../../guards/cors\";\nimport { CorsInit } from \"../../interfaces/cors\";\nimport { Middleware } from \"../../interfaces/middleware\";\n\nimport { CorsHandler } from \"./handler\";\n\n/**\n * Returns a `CORS` middleware instance.\n *\n * This middleware automatically handles Cross-Origin Resource Sharing (CORS)\n * for incoming requests, including preflight `OPTIONS` requests, and adds\n * appropriate headers to responses.\n *\n * @param init - Optional configuration for `CORS` behavior. See {@link CorsConfig}.\n * @returns A {@link Middleware} instance that can be used in your middleware chain.\n */\nexport function cors(init?: CorsInit): Middleware {\n assertCorsInit(init);\n return new CorsHandler(init);\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { match } from \"path-to-regexp\";\n\nimport { GET } from \"../../constants/methods\";\nimport { BadRequest, UpgradeRequired } from \"../../errors\";\nimport { Middleware } from \"../../interfaces/middleware\";\nimport { Worker } from \"../../interfaces/worker\";\n\nimport { hasConnectionHeader, hasUpgradeHeader, hasWebSocketVersion } from \"./utils\";\n\n/**\n * Middleware for validating WebSocket upgrade requests.\n *\n * - Only applies to `GET` requests.\n * - Matches requests against a specific path using `path-to-regexp` patterns.\n * - Validates that the request contains required WebSocket headers:\n * - `Connection: Upgrade`\n * - `Upgrade: websocket`\n * - `Sec-WebSocket-Version` matches the expected version\n * - Returns an error response if any validation fails.\n * - Otherwise, passes control to the next middleware or origin handler.\n */\nexport class WebSocketHandler implements Middleware {\n /**\n * Creates a new WebSocketHandler for a specific path.\n *\n * @param path - The request path this handler should intercept for WebSocket upgrades.\n * Supports dynamic segments using `path-to-regexp` syntax.\n */\n constructor(private readonly path: string) {}\n\n /**\n * Handles an incoming request, validating WebSocket upgrade headers.\n *\n * @param worker - The Worker instance containing the request.\n * @param next - Function to invoke the next middleware.\n * @returns A Response object if the request fails WebSocket validation,\n * or the result of `next()` if the request is valid or does not match.\n */\n public handle(worker: Worker, next: () => Promise<Response>): Promise<Response> {\n if (worker.request.method !== GET) {\n return next();\n }\n\n if (!this.isMatch(worker.request)) {\n return next();\n }\n\n const headers = worker.request.headers;\n if (!hasConnectionHeader(headers)) {\n return new BadRequest(\"Missing or invalid 'Connection' header\").response();\n }\n if (!hasUpgradeHeader(headers)) {\n return new BadRequest(\"Missing or invalid 'Upgrade' header\").response();\n }\n if (!hasWebSocketVersion(headers)) {\n return new UpgradeRequired().response();\n }\n\n return next();\n }\n\n /**\n * Checks if the request path matches the configured path for this handler.\n *\n * @param request - The incoming Request object.\n * @returns `true` if the request path matches, `false` otherwise.\n */\n private isMatch(request: Request): boolean {\n return match(this.path)(new URL(request.url).pathname) !== false;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../constants/headers\";\nimport { getHeaderValues } from \"../../utils/headers\";\n\nimport { WS_UPGRADE, WS_VERSION, WS_WEBSOCKET } from \"./constants\";\n\n/**\n * Checks if the `Connection` header includes the WebSocket upgrade token.\n *\n * @param headers - The Headers object to inspect.\n * @returns `true` if a WebSocket upgrade is requested via `Connection` header, `false` otherwise.\n */\nexport function hasConnectionHeader(headers: Headers): boolean {\n return getHeaderValues(headers, HttpHeader.CONNECTION).some(\n (value) => value.toLowerCase() === WS_UPGRADE,\n );\n}\n\n/**\n * Checks if the `Upgrade` header requests a WebSocket upgrade.\n *\n * @param headers - The Headers object to inspect.\n * @returns `true` if the `Upgrade` header is set to `websocket`, `false` otherwise.\n */\nexport function hasUpgradeHeader(headers: Headers): boolean {\n return getHeaderValues(headers, HttpHeader.UPGRADE).some(\n (value) => value.toLowerCase() === WS_WEBSOCKET,\n );\n}\n\n/**\n * Determines whether the request includes the correct WebSocket version.\n *\n * @param headers - The Headers object to inspect.\n * @returns `true` if `Sec-WebSocket-Version` matches the expected version, `false` otherwise.\n */\nexport function hasWebSocketVersion(headers: Headers): boolean {\n return headers.get(HttpHeader.SEC_WEBSOCKET_VERSION)?.trim() === WS_VERSION;\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Middleware } from \"../../interfaces/middleware\";\n\nimport { WebSocketHandler } from \"./handler\";\n\n/**\n * Returns a middleware that validates incoming WebSocket upgrade requests.\n *\n * - Only validates the upgrade request; it does **not** perform the actual WebSocket upgrade.\n * - Ensures the request:\n * - Uses the `GET` method.\n * - Matches the specified path, supporting `path-to-regexp` style patterns\n * (e.g., `/chat/:name`).\n * - Contains required WebSocket headers:\n * - `Connection: Upgrade`\n * - `Upgrade: websocket`\n * - `Sec-WebSocket-Version: 13`\n * - Returns an error response if validation fails, otherwise passes control to\n * the next middleware or origin handler.\n *\n * @param path - The URL path to intercept for WebSocket upgrades. Defaults to `/`.\n * Supports dynamic segments using `path-to-regexp` syntax.\n * @returns A {@link Middleware} instance that can be used in your middleware chain.\n *\n * @example\n * ```ts\n * app.use(websocket(\"/chat/:name\"));\n * ```\n */\nexport function websocket(path: string = \"/\"): Middleware {\n return new WebSocketHandler(path);\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isString } from \"./basic\";\n\n/**\n * Checks whether a value is binary data suitable for WebSocket transmission.\n *\n * A value is considered binary if it is an {@link ArrayBuffer} or any\n * {@link ArrayBufferView} (such as a `Uint8Array`, `DataView`, etc.).\n *\n * @param value - The value to check.\n * @returns `true` if `value` is an {@link ArrayBuffer} or {@link ArrayBufferView}, otherwise `false`.\n */\nexport function isBinary(value: unknown): value is ArrayBuffer | ArrayBufferView {\n return value instanceof ArrayBuffer || ArrayBuffer.isView(value);\n}\n\n/**\n * Checks whether a value can be sent over a WebSocket connection.\n *\n * A sendable value is either:\n * - a non-empty string, or\n * - a non-empty {@link ArrayBuffer} / {@link ArrayBufferView}.\n *\n * Empty strings and zero-length binary data are considered non-sendable.\n *\n * @param value - The value to check.\n * @returns `true` if `value` is a non-empty string or binary buffer, otherwise `false`.\n */\nexport function isSendable(value: unknown): value is string | ArrayBuffer | ArrayBufferView {\n if (isString(value)) return value.length > 0;\n if (isBinary(value)) return value.byteLength > 0;\n return false;\n}\n\n/**\n * Asserts that a value is a serializable object suitable for JSON encoding.\n *\n * This function ensures the value is a non-null object and that\n * `JSON.stringify()` succeeds without throwing an error.\n *\n * @param value - The value to validate.\n * @throws {TypeError} If `value` is not an object or cannot be serialized to JSON.\n */\nexport function assertSerializable(value: unknown): asserts value is object {\n if (value === null || typeof value !== \"object\") {\n throw new TypeError(\"WebSocket attachment must be an object\");\n }\n try {\n JSON.stringify(value);\n } catch {\n throw new TypeError(\"WebSocket attachment is not serializable\");\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CustomEventType,\n EventOptions,\n ExtendedEventListener,\n ExtendedEventMap,\n ExtendedEventType,\n} from \"../interfaces/websocket\";\n\n/**\n * Base class for managing WebSocket events, including both standard WebSocket events\n * and internal custom events (`open` and `warn`).\n *\n * This class wraps a native WebSocket instance (`server`) and provides:\n * - Delegation of standard events (`message`, `close`, etc.)\n * - Support for custom events used internally by the library:\n * - `open`: dispatched once when the connection is accepted\n * - `warn`: dispatched whenever a warning occurs\n *\n * Subclasses can call `warn()` or `open()` to trigger custom events.\n */\nexport abstract class WebSocketEvents {\n /** The underlying WebSocket server instance being wrapped. */\n protected readonly server: WebSocket;\n\n /** Internal map of custom event listeners. */\n private customListeners: {\n [K in ExtendedEventType]?: ((ev: ExtendedEventMap[K]) => void)[];\n } = {};\n\n /**\n * @param server - The native WebSocket instance to wrap.\n */\n constructor(server: WebSocket) {\n this.server = server;\n }\n\n /**\n * Adds an event listener for either a standard WebSocket event or a custom event.\n *\n * - Custom events: `open`, `warn`\n * - Standard events: `message`, `close`, `error`, etc.\n *\n * The `close` event is automatically set to `{ once: true }` if no options are provided.\n *\n * @param type - Event type to listen for.\n * @param listener - Callback invoked when the event occurs.\n * @param options - Optional event options (`once`).\n */\n public addEventListener<K extends ExtendedEventType>(\n type: K,\n listener: ExtendedEventListener<K>,\n options?: EventOptions,\n ): void {\n if (WebSocketEvents.isCustomEvent(type)) {\n let arr = this.customListeners[type];\n if (!arr) {\n arr = [];\n this.customListeners[type] = arr;\n }\n arr.push(listener);\n } else {\n const finalOptions = type === \"close\" ? { ...options, once: true } : options;\n this.server.addEventListener(\n type as keyof WebSocketEventMap,\n listener as EventListener,\n finalOptions,\n );\n }\n }\n\n /**\n * Removes a previously registered event listener.\n *\n * Works for both standard WebSocket events and custom events.\n *\n * @param type - Event type to remove.\n * @param listener - Listener function to remove.\n */\n public removeEventListener<K extends ExtendedEventType>(\n type: K,\n listener: ExtendedEventListener<K>,\n ): void {\n if (WebSocketEvents.isCustomEvent(type)) {\n const arr = this.customListeners[type];\n if (arr) {\n const index = arr.indexOf(listener);\n if (index !== -1) arr.splice(index, 1);\n }\n } else {\n this.server.removeEventListener(\n type as keyof WebSocketEventMap,\n listener as EventListener,\n );\n }\n }\n\n /**\n * Dispatches a custom event to all registered listeners.\n *\n * @param type - The custom event type (`open` or `warn`).\n * @param ev - Event object to pass to listeners.\n * @param once - If `true`, all listeners for this event are removed after dispatch.\n */\n private dispatch<K extends CustomEventType>(\n type: K,\n ev: ExtendedEventMap[K],\n once: boolean = false,\n ): void {\n const listeners = this.customListeners[type]?.slice() ?? [];\n if (once) {\n this.customListeners[type] = [];\n }\n for (const listener of listeners) {\n listener(ev);\n }\n }\n\n /**\n * Dispatches a `warn` event with a given message.\n *\n * Intended for internal use to notify listeners of warnings.\n *\n * @param msg - Warning message to emit.\n */\n protected warn(msg: string) {\n this.dispatch(\"warn\", { type: \"warn\", message: msg });\n }\n\n /**\n * Dispatches an `open` event.\n *\n * Intended to signal that the WebSocket has been accepted and is ready.\n * This event is dispatched only once.\n */\n protected open() {\n this.dispatch(\"open\", new Event(\"open\"), true);\n }\n\n /** Internal helper to determine if an event is a custom event. */\n private static isCustomEvent(type: ExtendedEventType): boolean {\n return [\"open\", \"warn\"].includes(type);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** Max close code a user can send */\nexport const WS_MAX_CLOSE_CODE = 4999;\n/** Max number of reason chars a user can send */\nexport const WS_MAX_REASON_CHARS = 123;\n\n/** WebSocket close codes */\nexport const CloseCode = {\n NORMAL: 1000,\n NO_STATUS: 1005,\n ABNORMAL: 1006,\n TLS_HANDSHAKE: 1015,\n} as const;\n\n/** WebSocket RESERVED close codes */\nexport const WS_RESERVED_CODES = new Set<number>([\n CloseCode.NO_STATUS,\n CloseCode.ABNORMAL,\n CloseCode.TLS_HANDSHAKE,\n]);\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isNumber, isString } from \"../guards/basic\";\n\nimport { CloseCode, WS_MAX_CLOSE_CODE, WS_MAX_REASON_CHARS, WS_RESERVED_CODES } from \"./constants\";\n\n/**\n * Normalizes a WebSocket close code to ensure it is safe to send.\n *\n * - Returns `CloseCode.NORMAL` if the code is undefined, out of range, or reserved.\n *\n * @param code - The optional close code to validate.\n * @returns A valid close code to use for WebSocket closure.\n */\nexport function safeCloseCode(code?: number): number {\n if (!isNumber(code)) return CloseCode.NORMAL;\n if (isCodeInRange(code) && !isReservedCode(code)) return code;\n return CloseCode.NORMAL;\n}\n\n/**\n * Determines whether a close code is within the valid WebSocket range.\n *\n * @param code - The code to validate.\n * @returns `true` if the code is within 1000–4999, `false` otherwise.\n */\nexport function isCodeInRange(code: number): boolean {\n return code >= CloseCode.NORMAL && code <= WS_MAX_CLOSE_CODE;\n}\n\n/**\n * Determines whether a close code is reserved by the WebSocket specification.\n *\n * @param code - The code to check.\n * @returns `true` if the code is reserved, `false` otherwise.\n */\nexport function isReservedCode(code: number): boolean {\n return WS_RESERVED_CODES.has(code);\n}\n\n/**\n * Sanitizes a close reason string to comply with WebSocket limits.\n *\n * - Removes non-printable ASCII characters.\n * - Truncates to the maximum allowed length (`WS_MAX_REASON_CHARS`).\n *\n * @param reason - The optional reason string to sanitize.\n * @returns A cleaned reason string or `undefined` if input is invalid.\n */\nexport function safeReason(reason?: string): string | undefined {\n if (!isString(reason)) return;\n return reason.replaceAll(/[^\\x20-\\x7E]/g, \"\").slice(0, WS_MAX_REASON_CHARS);\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { assertSerializable, isSendable } from \"../guards/websocket\";\nimport { WSAttachment } from \"../interfaces/websocket\";\n\nimport { WebSocketEvents } from \"./events\";\nimport { safeCloseCode, safeReason } from \"./utils\";\n\n/**\n * Base class providing core WebSocket functionality and attachment management.\n *\n * Extends `WebSocketEvents` to inherit custom event handling.\n *\n * @template A - Type of the attachment object associated with this WebSocket.\n */\nexport abstract class BaseWebSocket<A extends WSAttachment> extends WebSocketEvents {\n /** Tracks whether the WebSocket has been accepted. */\n protected accepted = false;\n\n /** The underlying WebSocket server instance. */\n protected readonly server: WebSocket;\n\n /**\n * Initializes the base WebSocket wrapper.\n *\n * Registers a listener to handle the underlying WebSocket `close` event.\n *\n * @param server - The underlying WebSocket instance.\n */\n constructor(server: WebSocket) {\n super(server);\n this.server = server;\n this.server.addEventListener(\"close\", this.onclose);\n }\n\n /**\n * Sends a message over the WebSocket if it is open.\n *\n * Performs validation to ensure the WebSocket is in an open state\n * and the data is non-empty and sendable. Emits a warning if not.\n *\n * @param data - The message to send, as a string or binary data.\n */\n public send(data: string | ArrayBuffer | ArrayBufferView): void {\n if (this.isState(WebSocket.CONNECTING, WebSocket.CLOSED)) {\n this.warn(\"Cannot send: WebSocket not open\");\n return;\n }\n if (!isSendable(data)) {\n this.warn(\"Cannot send: empty or invalid data\");\n return;\n }\n\n this.server.send(data);\n }\n\n /**\n * Returns the current attachment associated with this WebSocket.\n *\n * Attachments are stored as serialized objects on the underlying WebSocket.\n *\n * @returns Readonly attachment object of type `A`.\n */\n public get attachment(): Readonly<A> {\n return (this.server.deserializeAttachment() ?? {}) as A;\n }\n\n /**\n * Updates the attachment object for this WebSocket.\n *\n * Merges the provided partial attachment with the current attachment,\n * ensures it is serializable, and stores it on the underlying WebSocket.\n *\n * @param attachment - Partial or full attachment object to store,\n * or `null` to clear the attachment.\n */\n public attach(attachment?: Partial<A> | null): void {\n if (attachment === undefined) return;\n if (attachment === null) {\n this.server.serializeAttachment({});\n } else {\n const current = this.attachment;\n const merged = { ...current, ...attachment };\n assertSerializable(merged);\n this.server.serializeAttachment(merged);\n }\n }\n\n /**\n * Returns the current WebSocket ready state.\n *\n * If the WebSocket has not been accepted, returns `WebSocket.CONNECTING`.\n *\n * @returns The ready state of the WebSocket.\n */\n public get readyState(): number {\n if (!this.accepted) return WebSocket.CONNECTING;\n return this.server.readyState;\n }\n\n /**\n * Checks if the current ready state matches any of the provided states.\n *\n * @param states - One or more WebSocket state constants to check.\n * @returns `true` if the current state matches any provided, `false` otherwise.\n */\n public isState(...states: number[]): boolean {\n return states.includes(this.readyState);\n }\n\n /**\n * Closes the WebSocket safely.\n *\n * Removes the internal `close` listener and closes the underlying WebSocket\n * using validated close code and sanitized reason.\n *\n * @param code - Optional WebSocket close code.\n * @param reason - Optional reason for closing the WebSocket.\n */\n public close(code?: number, reason?: string): void {\n this.server.removeEventListener(\"close\", this.onclose);\n this.server.close(safeCloseCode(code), safeReason(reason));\n }\n\n /** Internal handler for the underlying WebSocket `close` event. */\n private readonly onclose = (event: CloseEvent): void => {\n this.close(event.code, event.reason);\n };\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { WebSocketConnection, WSAttachment } from \"../interfaces/websocket\";\n\nimport { BaseWebSocket } from \"./base\";\n\n/**\n * Internal base for managing a new WebSocket connection.\n *\n * - Creates a WebSocket pair and stores the client side internally.\n * - Provides methods to accept the server WebSocket and retrieve the client.\n * - Ensures the `open` event is dispatched when the connection is accepted.\n * - Tracks whether the connection has already been accepted to prevent multiple acceptances.\n *\n * @template A - Type of the attachment object for this connection.\n */\nexport abstract class NewConnectionBase<A extends WSAttachment>\n extends BaseWebSocket<A>\n implements WebSocketConnection<A>\n{\n /** The client-facing end of the WebSocket pair. */\n private readonly client: WebSocket;\n\n /**\n * Creates a new WebSocket pair and initializes the server side.\n *\n * The client side is stored internally and returned upon acceptance.\n */\n public constructor() {\n const pair = new WebSocketPair();\n const [client, server] = [pair[0], pair[1]];\n super(server);\n this.client = client;\n }\n\n /**\n * Accepts the server WebSocket and returns the client WebSocket.\n *\n * If already accepted, returns the existing client WebSocket.\n * Otherwise, uses a Durable Object state to accept the connection\n * and marks it as ready.\n *\n * @param ctx - DurableObjectState used to accept the WebSocket.\n * @param tags - Optional array of tags to attach to the WebSocket.\n * @returns Readonly client WebSocket ready for use.\n */\n public acceptWebSocket(ctx: DurableObjectState, tags?: string[]): Readonly<WebSocket> {\n if (this.accepted) return this.client;\n ctx.acceptWebSocket(this.server, tags);\n return this.ready();\n }\n\n /**\n * Accepts the server WebSocket and returns the client WebSocket.\n *\n * If already accepted, returns the existing client WebSocket.\n * Otherwise, calls the internal server accept method and marks\n * the connection as ready.\n *\n * @returns Readonly client WebSocket ready for use.\n */\n public accept(): Readonly<WebSocket> {\n if (this.accepted) return this.client;\n this.server.accept();\n return this.ready();\n }\n\n /**\n * Marks the WebSocket connection as ready.\n *\n * Sets the accepted flag, dispatches the `open` event,\n * and returns the client WebSocket.\n *\n * @returns Client WebSocket ready for use.\n */\n private ready(): WebSocket {\n this.accepted = true;\n this.open();\n\n return this.client;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { WebSocketConnection, WSAttachment } from \"../interfaces/websocket\";\n\nimport { BaseWebSocket } from \"./base\";\n\n/**\n * Internal base for a WebSocket connection that has already been restored.\n *\n * - Marks the connection as accepted immediately upon construction.\n * - Overrides acceptance methods to prevent re-accepting an already-active WebSocket.\n * - Throws an error if `accept()` or `acceptWebSocket()` is called.\n *\n * @template A - Type of the attachment object for this connection.\n */\nexport abstract class RestoredConnectionBase<A extends WSAttachment>\n extends BaseWebSocket<A>\n implements WebSocketConnection<A>\n{\n constructor(ws: WebSocket) {\n super(ws);\n this.accepted = true;\n }\n\n /** Not supported for restored connections; throws an error. */\n public accept(): Readonly<WebSocket> {\n throw new Error(\"Do not call accept() on restore\");\n }\n\n /** Not supported for restored connections; throws an error. */\n public acceptWebSocket(): Readonly<WebSocket> {\n throw new Error(\"Do not call acceptWebSocket() on restore\");\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { WebSocketConnection, WSAttachment } from \"../interfaces/websocket\";\n\nimport { NewConnectionBase } from \"./new\";\nimport { RestoredConnectionBase } from \"./restore\";\n\n/**\n * Manages active WebSocket connections in a Cloudflare Workers environment.\n *\n * Provides a simple interface for creating, restoring, and managing\n * WebSocket connections with optional attachments. Users can:\n *\n * - Create new WebSocket connections (`create`) and attach arbitrary data.\n * - Accept connections using the standard WebSocket API (`accept`).\n * - Accept connections using the hibernatable WebSocket API (`acceptWebSocket`),\n * which allows the connection to be put to sleep when inactive.\n * - Restore existing WebSockets into a managed session (`restore`, `restoreAll`),\n * maintaining their hibernation state.\n * - Iterate over active connections or retrieve a connection by its WebSocket instance.\n * - Close a connection cleanly with optional code and reason (`close`).\n *\n * @template A - Type of attachment data stored on each WebSocket connection.\n */\nexport class WebSocketSessions<A extends WSAttachment = WSAttachment> {\n /** @internal Map of active WebSocket to their connection wrapper. */\n private readonly map = new Map<WebSocket, WebSocketConnection<A>>();\n\n /**\n * Create a new WebSocket connection and optionally attach user data.\n *\n * @param attachment - Partial attachment object to initialize the connection with.\n * @returns A `WebSocketConnection` instance ready for accepting and sending messages.\n */\n public create(attachment?: Partial<A>): WebSocketConnection<A> {\n class NewConnection extends NewConnectionBase<A> {\n constructor(private readonly sessions: WebSocketSessions<A>) {\n super();\n }\n\n public override accept(): WebSocket {\n this.addEventListener(\"close\", () => this.sessions.unregister(this.server));\n this.sessions.register(this.server, this);\n return super.accept();\n }\n\n public override acceptWebSocket(ctx: DurableObjectState, tags?: string[]): WebSocket {\n this.sessions.register(this.server, this);\n return super.acceptWebSocket(ctx, tags);\n }\n }\n\n const connection = new NewConnection(this);\n connection.attach(attachment);\n return connection;\n }\n\n /**\n * Wraps an existing WebSocket in a managed connection session.\n *\n * @param ws - An existing WebSocket to restore.\n * @returns A `WebSocketConnection` representing the restored session.\n */\n public restore(ws: WebSocket): WebSocketConnection<A> {\n class RestoredConnection extends RestoredConnectionBase<A> {\n constructor(sessions: WebSocketSessions<A>, restore: WebSocket) {\n super(restore);\n sessions.register(this.server, this);\n }\n }\n return new RestoredConnection(this, ws);\n }\n\n /**\n * Restores multiple WebSockets into managed sessions at once.\n *\n * @param all - Array of WebSocket instances to restore.\n * @returns Array of `WebSocketConnections` restored.\n */\n public restoreAll(all: WebSocket[]): ReadonlyArray<WebSocketConnection<A>> {\n const restored: WebSocketConnection<A>[] = [];\n for (const ws of all) {\n restored.push(this.restore(ws));\n }\n return restored;\n }\n\n /**\n * Retrieves the managed connection for a specific WebSocket, if any.\n *\n * @param ws - WebSocket instance.\n * @returns Corresponding `WebSocketConnection` or `undefined` if not managed.\n */\n public get(ws: WebSocket): WebSocketConnection<A> | undefined {\n return this.map.get(ws);\n }\n\n /**\n * Selects the managed `WebSocketConnection` objects corresponding to the given WebSockets.\n *\n * @param sockets - Array of WebSocket instances to resolve.\n * @returns Array of corresponding `WebSocketConnection` objects.\n */\n public select(sockets: WebSocket[]): WebSocketConnection<A>[] {\n const result: WebSocketConnection<A>[] = [];\n for (const ws of sockets) {\n const con = this.map.get(ws);\n if (con) result.push(con);\n }\n return result;\n }\n\n /**\n * Returns an iterator over all active `WebSocketConnection` objects\n * managed by this session.\n *\n * Useful for iterating over all connections to perform actions such as\n * broadcasting messages.\n *\n * @returns Iterable iterator of all active `WebSocketConnection` objects.\n */\n public values(): IterableIterator<WebSocketConnection<A>> {\n return this.map.values();\n }\n\n /**\n * Returns an iterator over all active raw `WebSocket` instances\n * currently tracked by this session.\n *\n * @returns Iterable iterator of all active `WebSocket` instances.\n */\n public keys(): IterableIterator<WebSocket> {\n return this.map.keys();\n }\n\n /**\n * Closes a managed WebSocket connection with optional code and reason.\n *\n * @param ws - WebSocket to close.\n * @param code - Optional WebSocket close code.\n * @param reason - Optional reason string.\n * @returns `true` if the connection was managed and removed, `false` otherwise.\n */\n public close(ws: WebSocket, code?: number, reason?: string): boolean {\n const con = this.get(ws);\n if (con) con.close(code, reason);\n\n return this.unregister(ws);\n }\n\n /** Iterates over all active WebSocket connections. */\n public *[Symbol.iterator](): IterableIterator<WebSocketConnection<A>> {\n yield* this.values();\n }\n\n /** Registers a connection internally. */\n private register(ws: WebSocket, con: WebSocketConnection<A>): void {\n this.map.set(ws, con);\n }\n\n /** Un-registers a connection internally. */\n private unregister(ws: WebSocket): boolean {\n return this.map.delete(ws);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Middleware } from \"../interfaces/middleware\";\n\n/**\n * Asserts at runtime that a value implements the `Middleware` interface.\n *\n * @param value - The value to check.\n * @throws TypeError If `handler` does not have a `handle` method.\n */\nexport function assertMiddleware(value: unknown): asserts value is Middleware {\n if (\n value === null ||\n typeof value !== \"object\" ||\n typeof (value as Middleware).handle !== \"function\"\n ) {\n throw new TypeError(\n \"Handler must implement the Middleware interface (have a handle method).\",\n );\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GET, HEAD, Method } from \"../constants/methods\";\nimport { assertMethods, isMethod } from \"../guards/methods\";\nimport { FetchHandler } from \"../interfaces/fetch\";\nimport { Worker, WorkerClass } from \"../interfaces/worker\";\n\n/**\n * Provides the foundational structure for handling requests,\n * environment bindings, and the worker execution context.\n *\n * Features:\n * - Holds the current `Request` object (`request` getter).\n * - Provides access to environment bindings (`env` getter).\n * - Provides access to the worker execution context (`ctx` getter).\n * - Subclasses must implement `fetch()` to process the request.\n */\nexport abstract class BaseWorker implements Worker {\n constructor(\n private readonly _request: Request,\n private readonly _env: Env,\n private readonly _ctx: ExecutionContext,\n ) {}\n\n /** The Request object associated with this worker invocation */\n public get request(): Request {\n return this._request;\n }\n\n /** Environment bindings (e.g., KV, secrets, or other globals) */\n public get env(): Env {\n return this._env;\n }\n\n /** Execution context for background tasks or `waitUntil` */\n public get ctx(): ExecutionContext {\n return this._ctx;\n }\n\n /**\n * Dispatches the incoming request to the appropriate handler and produces a response.\n *\n * Subclasses must implement this method to define how the worker generates a `Response`\n * for the current request. This is the central point where request processing occurs.\n *\n * @returns A Promise that resolves to the `Response` for the request.\n */\n protected abstract dispatch(): Promise<Response>;\n\n /**\n * Determines whether a given HTTP method is allowed for this worker.\n *\n * - GET and HEAD are **always allowed**, in compliance with RFC 9110,\n * even if they are not explicitly listed in `getAllowedMethods()`.\n * - Other methods are allowed only if included in the array returned by\n * `getAllowedMethods()` and are valid HTTP methods.\n *\n * @param method - The HTTP method to check (e.g., \"GET\", \"POST\").\n * @returns `true` if the method is allowed, `false` otherwise.\n */\n public isAllowed(method: string): boolean {\n const methods = this.getAllowedMethods();\n assertMethods(methods);\n\n // GET and HEAD are always allowed per RFC\n if (method === GET || method === HEAD) return true;\n\n return isMethod(method) && methods.includes(method);\n }\n\n public abstract getAllowedMethods(): Method[];\n\n /**\n * Creates a new instance of the current Worker subclass.\n *\n * @param request - The {@link Request} to pass to the new worker instance.\n * @returns A new worker instance of the same subclass as `this`.\n */\n protected create(request: Request): this {\n const ctor = this.constructor as WorkerClass<this>;\n return new ctor(request, this.env, this.ctx);\n }\n\n /**\n * Process the {@link Request} and produce a {@link Response}.\n *\n * @returns A {@link Response} promise for the {@link Request}.\n */\n public abstract fetch(): Promise<Response>;\n\n /**\n * Simplify and standardize {@link Response} creation by extending {@link WorkerResponse}\n * or any of its subclasses and passing to this method.\n *\n * Or directly use any of the built-in classes.\n *\n * ```ts\n * this.response(TextResponse, \"Hello World!\")\n * ```\n *\n * @param ResponseClass The response class to instantiate\n * @param args Additional constructor arguments\n * @returns A Promise resolving to the {@link Response} object\n */\n protected response<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Ctor extends new (...args: any[]) => { response(): Promise<Response> },\n >(ResponseClass: Ctor, ...args: ConstructorParameters<Ctor>): Promise<Response> {\n return new ResponseClass(...args).response();\n }\n\n /**\n * **Ignite** your `Worker` implementation into a Cloudflare handler.\n *\n * @returns A `FetchHandler` that launches a new worker instance for each request.\n *\n * ```ts\n * export default MyWorker.ignite();\n * ```\n */\n public static ignite<W extends Worker>(this: WorkerClass<W>): FetchHandler {\n return {\n fetch: (request: Request, env: Env, ctx: ExecutionContext) =>\n new this(request, env, ctx).fetch(),\n };\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { MethodNotAllowed } from \"../errors\";\nimport { assertMiddleware } from \"../guards/middleware\";\nimport { Middleware } from \"../interfaces/middleware\";\n\nimport { BaseWorker } from \"./base\";\n\n/** Base worker for handling middleware chains. */\nexport abstract class MiddlewareWorker extends BaseWorker {\n /** Middleware handlers registered for this worker. */\n protected readonly middlewares: Middleware[] = [];\n\n /**\n * Hook for subclasses to perform any initialization.\n */\n protected init(): void | Promise<void> {\n return;\n }\n\n /**\n * Add one or more middleware instances to this worker.\n *\n * The middleware will run for every request handled by this worker,\n * in the order they are added.\n *\n * @param middleware - One or more middleware instances to run.\n * @returns `this` to allow chaining multiple `.use()` calls.\n */\n public use(...middleware: Middleware[]): this {\n for (const value of middleware) {\n assertMiddleware(value);\n }\n\n this.middlewares.push(...middleware);\n return this;\n }\n\n /**\n * Executes the middleware chain and dispatches the request if the method\n * is allowed by the worker.\n *\n * @returns The Response produced by the last middleware or `dispatch()`.\n */\n public override async fetch(): Promise<Response> {\n /**\n * Allow subclasses to add middleware to be used for the request.\n */\n await this.init();\n\n const chain = this.middlewares.reduceRight(\n (next, handler) => () => handler.handle(this, next),\n () => {\n if (!this.isAllowed(this.request.method)) {\n return this.response(MethodNotAllowed, this);\n }\n return this.dispatch();\n },\n );\n return chain();\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GET, HEAD, Method, OPTIONS } from \"../constants/methods\";\nimport { InternalServerError, MethodNotAllowed, MethodNotImplemented, NotFound } from \"../errors\";\nimport { Head, Options } from \"../responses\";\n\nimport { MiddlewareWorker } from \"./middleware\";\n\n/**\n * Basic worker class providing HTTP method dispatching and error handling.\n */\nexport abstract class BasicWorker extends MiddlewareWorker {\n /**\n * Entry point to handle a fetch request.\n */\n public override async fetch(): Promise<Response> {\n try {\n return await super.fetch();\n } catch (error) {\n console.error(error);\n return this.response(InternalServerError);\n }\n }\n\n /**\n * Dispatches the request to the method-specific handler.\n */\n protected override dispatch(): Promise<Response> {\n const method = this.request.method as Method;\n const handler: Record<Method, () => Promise<Response>> = {\n GET: () => this.get(),\n PUT: () => this.put(),\n HEAD: () => this.head(),\n POST: () => this.post(),\n PATCH: () => this.patch(),\n DELETE: () => this.delete(),\n OPTIONS: () => this.options(),\n };\n\n return (handler[method] ?? (() => this.response(MethodNotAllowed, this)))();\n }\n\n /** Override and implement this method for `GET` requests. */\n protected get(): Promise<Response> {\n return this.response(NotFound);\n }\n\n /** Override and implement this method for `PUT` requests. */\n protected put(): Promise<Response> {\n return this.response(MethodNotImplemented, this);\n }\n\n /** Override and implement this method for `POST` requests. */\n protected post(): Promise<Response> {\n return this.response(MethodNotImplemented, this);\n }\n\n /** Override and implement this method for `PATCH` requests. */\n protected patch(): Promise<Response> {\n return this.response(MethodNotImplemented, this);\n }\n\n /** Override and implement this method for `DELETE` requests. */\n protected delete(): Promise<Response> {\n return this.response(MethodNotImplemented, this);\n }\n\n /** Returns the default `OPTIONS` response. */\n protected options(): Promise<Response> {\n return this.response(Options, this);\n }\n\n /**\n * Default handler for `HEAD` requests.\n * Performs a `GET` request and removes the body for `HEAD` semantics.\n *\n * Usually does not need to be overridden as this behavior covers\n * standard `HEAD` requirements.\n */\n protected async head(): Promise<Response> {\n const worker = this.create(\n new Request(this.request.url, { method: GET, headers: this.request.headers }),\n );\n return this.response(Head, await worker.fetch());\n }\n\n /**\n * Returns the HTTP methods allowed by this worker.\n *\n * - GET and HEAD are always allowed per RFC 9110, even if subclasses do not include them here.\n * - OPTIONS is included by default since a default handler is implemented.\n * - Subclasses can override this method to allow additional methods or change the defaults.\n */\n public getAllowedMethods(): Method[] {\n return [GET, HEAD, OPTIONS];\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { match } from \"path-to-regexp\";\n\nimport { Method } from \"./constants/methods\";\nimport { MatchedRoute, PathParams, Route, RouteTable } from \"./interfaces/route\";\n\n/**\n * Container for route definitions and matching logic.\n * Implements Iterable to allow iteration over all routes.\n */\nexport class Routes implements Iterable<Route> {\n /** Internal array of registered routes */\n private readonly routes: Route[] = [];\n\n /**\n * Add routes to the router.\n *\n * Accepts any iterable of [method, path, handler] tuples.\n * This includes arrays, Sets, or generators.\n *\n * @param routes - Iterable of route tuples to add.\n */\n public add(routes: RouteTable): void {\n for (const [method, path, handler] of routes) {\n const matcher = match<PathParams>(path);\n this.routes.push({ method, matcher, handler });\n }\n }\n\n /**\n * Attempt to match a URL against the registered routes.\n *\n * @param method - HTTP method of the request\n * @param url - Full URL string to match against\n * @returns A MatchedRoute object if a route matches, otherwise null\n */\n public match(method: Method, url: string): MatchedRoute | null {\n const pathname = new URL(url).pathname;\n\n for (const route of this) {\n if (route.method !== method) continue;\n\n const found = route.matcher(pathname);\n if (found) return { route, params: found.params };\n }\n\n return null;\n }\n\n /**\n * Iterate over all registered routes.\n */\n public *[Symbol.iterator](): Iterator<Route> {\n yield* this.routes;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Method } from \"../constants/methods\";\nimport { NotFound } from \"../errors\";\nimport { RouteHandler, RouteTable } from \"../interfaces/route\";\nimport { WorkerClass } from \"../interfaces/worker\";\nimport { Routes } from \"../routes\";\n\nimport { BaseWorker } from \"./base\";\nimport { BasicWorker } from \"./basic\";\n\n/**\n * Base worker supporting route-based request handling.\n *\n * Subclass `RouteWorker` to define a worker with multiple route handlers.\n *\n * Routes can be registered individually via `route()` or in bulk via `routes()`.\n */\nexport abstract class RouteWorker extends BasicWorker {\n /** Internal table of registered routes. */\n private readonly _routes: Routes = new Routes();\n\n /**\n * Registers a single new route in the worker.\n *\n * When a request matches the specified method and path, the provided handler\n * will be executed. The handler can be either:\n * - A function that receives URL parameters, or\n * - A Worker subclass that will handle the request.\n *\n * @param method - HTTP method for the route (`GET`, `POST`, etc.).\n * @param path - URL path pattern (path-to-regexp, e.g., \"/users/:id\").\n * @param handler - The function or Worker class to run when the route matches.\n * @returns The current worker instance, allowing method chaining.\n */\n protected route(method: Method, path: string, handler: RouteHandler): this {\n this.routes([[method, path, handler]]);\n return this;\n }\n\n /**\n * Registers multiple routes at once in the worker.\n *\n * Each route should be a tuple `[method, path, handler]` where:\n * - `method` - HTTP method for the route (`GET`, `POST`, etc.).\n * - `path` - URL path pattern (path-to-regexp e.g., \"/users/:id\").\n * - `handler` - A function that receives URL parameters or a Worker subclass\n * that will handle the request.\n *\n * @param routes - An iterable of routes to register. Each item is a `[method, path, handler]` tuple.\n * @returns The current worker instance, allowing method chaining.\n */\n protected routes(routes: RouteTable): this {\n this._routes.add(routes);\n return this;\n }\n\n /**\n * Matches the incoming request against registered routes and dispatches it.\n *\n * If a route is found:\n * - If the handler is a Worker class, a new instance is created and its `fetch()` is called.\n * - If the handler is a callback function, it is invoked with the extracted path parameters.\n *\n * If no route matches, the request is passed to the superclass ({@link BasicWorker})\n * `dispatch()` handler.\n *\n * @returns A `Promise<Response>` from the matched handler or parent dispatch.\n */\n protected override async dispatch(): Promise<Response> {\n const found = this._routes.match(this.request.method as Method, this.request.url);\n if (!found) return super.dispatch();\n\n const { handler } = found.route;\n if (RouteWorker.isWorkerClass(handler)) {\n return new handler(this.request, this.env, this.ctx).fetch();\n }\n return handler.call(this, found.params);\n }\n\n /**\n * Runtime type guard to check if a given handler is a Worker class.\n *\n * A Worker class is any class that extends `BaseWorker`.\n *\n * @param handler - The constructor function to test.\n * @returns `true` if `handler` is a subclass of `BaseWorker` at runtime, `false` otherwise.\n */\n private static isWorkerClass(handler: RouteHandler): handler is WorkerClass {\n return Object.prototype.isPrototypeOf.call(BaseWorker.prototype, handler.prototype);\n }\n\n protected override put(): Promise<Response> {\n return this.response(NotFound);\n }\n\n protected override post(): Promise<Response> {\n return this.response(NotFound);\n }\n\n protected override patch(): Promise<Response> {\n return this.response(NotFound);\n }\n\n protected override delete(): Promise<Response> {\n return this.response(NotFound);\n }\n}\n"],"mappings":"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","names":["StatusCodes","CacheLib","CacheControl","Method","GET","PUT","HEAD","POST","PATCH","DELETE","OPTIONS","Time","isStringArray","value","item","isString","isNumber","isBoolean","assertCacheInit","value","name","getKey","assertCacheName","assertGetKey","isString","assertKey","sortSearchParams","request","url","stripSearchParams","CachePolicy","rules","worker","getCached","next","rule","StatusCodes","HttpHeader","FORBIDDEN_304_HEADERS","FORBIDDEN_204_HEADERS","lexCompare","a","b","setHeader","headers","key","value","raw","values","v","lexCompare","mergeHeader","merged","getHeaderValues","filterHeaders","keys","VARY_CACHE_URL","VARY_WILDCARD","isCacheable","request","response","StatusCodes","GET","HttpHeader","getCacheControl","responseCacheControl","ttl","getVaryHeader","headers","CacheControl","getFilteredVary","getHeaderValues","vary","values","h","value","lexCompare","getVaryKey","key","varyPairs","filtered","header","normalizeVaryValue","encoded","base64UrlEncode","name","str","utf8","binary","byte","RANGE_REGEX","ETAG_WEAK_PREFIX","WILDCARD_ETAG","getRange","request","range","HttpHeader","match","start","end","isPreconditionFailed","ifMatch","etag","found","isNotModified","ifNoneMatch","normalizeEtag","array","search","value","toDate","isString","date","getCacheValidators","headers","getHeaderValues","hasCacheValidator","ifModifiedSince","ifUnmodifiedSince","getContentLength","lengthHeader","length","isNumber","CacheControlRule","worker","next","cache","getCacheControl","hasCacheValidator","getReasonPhrase","StatusCodes","METHOD_SET","Method","isMethod","value","isString","isMethodArray","assertMethods","desc","WS_UPGRADE","WS_WEBSOCKET","getReasonPhrase","StatusCodes","UTF8_CHARSET","assertOctetStreamInit","value","obj","size","isNumber","offset","length","withCharset","mediaType","charset","BaseResponse","StatusCodes","withCharset","UTF8_CHARSET","getReasonPhrase","key","value","setHeader","mergeHeader","HttpHeader","filterHeaders","FORBIDDEN_204_HEADERS","FORBIDDEN_304_HEADERS","CacheResponse","cache","CacheControl","WorkerResponse","body","CopyResponse","response","NotModified","SuccessResponse","status","JsonResponse","json","HtmlResponse","charset","TextResponse","OctetStream","_OctetStream","stream","init","assertOctetStreamInit","normalized","size","offset","length","R2ObjectStream","_R2ObjectStream","source","useCache","range","WebSocketUpgrade","client","Head","get","Options","worker","allowed","GET","HEAD","assertMethods","HttpError","JsonResponse","status","details","json","getReasonPhrase","CacheControl","BadRequest","StatusCodes","Unauthorized","Forbidden","NotFound","MethodNotAllowed","worker","methods","assertMethods","HttpHeader","PreconditionFailed","UpgradeRequired","InternalServerError","NotImplemented","MethodNotImplemented","ServiceUnavailable","StatusCodes","ValidationRule","worker","next","response","StatusCodes","header","validators","getCacheValidators","MatchRule","ValidationRule","response","HttpHeader","IfMatchRule","etag","validators","isPreconditionFailed","PreconditionFailed","IfNoneMatchRule","isNotModified","NotModified","MethodRule","worker","next","GET","HEAD","response","Head","LastModifiedRule","ValidationRule","response","toDate","HttpHeader","ModifiedSinceRule","lastModified","validators","modifiedSince","NotModified","UnmodifiedSinceRule","unmodifiedSince","PreconditionFailed","StatusCodes","RangeRule","worker","next","range","getRange","response","StatusCodes","length","getContentLength","SecurityRule","worker","next","headers","HttpHeader","UpgradeRule","worker","next","HttpHeader","VariantResponse","_VariantResponse","WorkerResponse","vary","filtered","getFilteredVary","HttpHeader","source","variant","getHeaderValues","cacheControl","CacheControl","before","response","incoming","getCacheControl","incomingTTL","currentTTL","CacheHandler","init","name","getKey","sortSearchParams","worker","next","cache","cacheResponse","CachePolicy","CacheControlRule","MethodRule","UpgradeRule","SecurityRule","RangeRule","ModifiedSinceRule","IfNoneMatchRule","UnmodifiedSinceRule","IfMatchRule","response","request","key","VariantResponse","vary","varyKey","getVaryKey","isCacheable","clone","getVaryHeader","cached","isCachedVariant","variantResponse","assertKey","cache","init","assertCacheInit","CacheHandler","assertCorsInit","value","obj","isStringArray","isBoolean","isNumber","ALLOW_ALL_ORIGINS","SKIP_CORS_STATUSES","StatusCodes","defaultCorsConfig","HttpHeader","Time","options","response","worker","cors","copy","CopyResponse","origin","getOrigin","deleteCorsHeaders","setVaryOrigin","setAllowOrigin","setAllowCredentials","setAllowMethods","setAllowHeaders","setMaxAge","apply","setExposedHeaders","headers","allowAllOrigins","mergeHeader","HttpHeader","setHeader","ALLOW_ALL_ORIGINS","allowed","assertMethods","maxAge","skipCors","status","SKIP_CORS_STATUSES","request","CorsHandler","init","defaultCorsConfig","worker","next","response","OPTIONS","options","skipCors","apply","cors","init","assertCorsInit","CorsHandler","match","hasConnectionHeader","headers","getHeaderValues","HttpHeader","value","WS_UPGRADE","hasUpgradeHeader","WS_WEBSOCKET","hasWebSocketVersion","WebSocketHandler","path","worker","next","GET","headers","hasConnectionHeader","hasUpgradeHeader","hasWebSocketVersion","UpgradeRequired","BadRequest","request","match","websocket","path","WebSocketHandler","isBinary","value","isSendable","isString","assertSerializable","WebSocketEvents","_WebSocketEvents","server","type","listener","options","arr","finalOptions","index","ev","once","listeners","msg","CloseCode","WS_RESERVED_CODES","safeCloseCode","code","isNumber","isCodeInRange","isReservedCode","CloseCode","WS_RESERVED_CODES","safeReason","reason","isString","BaseWebSocket","WebSocketEvents","server","data","isSendable","attachment","merged","assertSerializable","states","code","reason","safeCloseCode","safeReason","event","NewConnectionBase","BaseWebSocket","pair","client","server","ctx","tags","RestoredConnectionBase","BaseWebSocket","ws","WebSocketSessions","attachment","NewConnection","NewConnectionBase","sessions","ctx","tags","connection","ws","RestoredConnection","RestoredConnectionBase","restore","all","restored","sockets","result","con","code","reason","assertMiddleware","value","BaseWorker","_request","_env","_ctx","method","methods","assertMethods","GET","HEAD","isMethod","request","ctor","ResponseClass","args","env","ctx","MiddlewareWorker","BaseWorker","middleware","value","assertMiddleware","next","handler","MethodNotAllowed","BasicWorker","MiddlewareWorker","error","InternalServerError","method","MethodNotAllowed","NotFound","MethodNotImplemented","Options","worker","GET","Head","HEAD","OPTIONS","match","Routes","routes","method","path","handler","matcher","url","pathname","route","found","RouteWorker","_RouteWorker","BasicWorker","Routes","method","path","handler","routes","found","BaseWorker","NotFound"]}
1
+ {"version":3,"sources":["../src/constants/index.ts","../src/constants/cache.ts","../src/constants/methods.ts","../src/constants/time.ts","../src/guards/basic.ts","../src/guards/cache.ts","../src/middleware/cache/keys.ts","../src/middleware/cache/policy.ts","../src/middleware/cache/utils.ts","../src/constants/headers.ts","../src/utils/compare.ts","../src/utils/headers.ts","../src/middleware/cache/rules/utils.ts","../src/middleware/cache/rules/control.ts","../src/errors.ts","../src/guards/methods.ts","../src/middleware/websocket/constants.ts","../src/responses.ts","../src/constants/media.ts","../src/guards/responses.ts","../src/utils/media.ts","../src/middleware/cache/rules/validation.ts","../src/middleware/cache/rules/etag.ts","../src/middleware/cache/rules/method.ts","../src/middleware/cache/rules/modified.ts","../src/middleware/cache/rules/range.ts","../src/middleware/cache/rules/security.ts","../src/middleware/cache/rules/upgrade.ts","../src/middleware/cache/variant.ts","../src/middleware/cache/handler.ts","../src/middleware/cache/cache.ts","../src/guards/cors.ts","../src/middleware/cors/constants.ts","../src/middleware/cors/utils.ts","../src/middleware/cors/handler.ts","../src/middleware/cors/cors.ts","../src/middleware/websocket/handler.ts","../src/middleware/websocket/utils.ts","../src/middleware/websocket/websocket.ts","../src/guards/websocket.ts","../src/websocket/events.ts","../src/websocket/constants.ts","../src/websocket/utils.ts","../src/websocket/base.ts","../src/websocket/new.ts","../src/websocket/restore.ts","../src/websocket/sessions.ts","../src/guards/middleware.ts","../src/workers/base.ts","../src/workers/middleware.ts","../src/workers/basic.ts","../src/routes.ts","../src/workers/route.ts"],"sourcesContent":["/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * https://github.com/prettymuchbryce/http-status-codes\n */\nexport { StatusCodes } from \"http-status-codes\";\n\nexport * from \"./cache\";\nexport * from \"./methods\";\nexport * from \"./time\";\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport CacheLib from \"cache-control-parser\";\n\n/**\n * @see {@link https://github.com/etienne-martin/cache-control-parser | cache-control-parser}\n */\nexport type CacheControl = CacheLib.CacheControl;\nexport const CacheControl = {\n parse: CacheLib.parse,\n stringify: CacheLib.stringify,\n\n /** A CacheControl directive that disables all caching. */\n DISABLE: Object.freeze({\n \"no-cache\": true,\n \"no-store\": true,\n \"must-revalidate\": true,\n \"max-age\": 0,\n }) satisfies CacheControl,\n};\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Standard HTTP request methods.\n */\nexport enum Method {\n GET = \"GET\",\n PUT = \"PUT\",\n HEAD = \"HEAD\",\n POST = \"POST\",\n PATCH = \"PATCH\",\n DELETE = \"DELETE\",\n OPTIONS = \"OPTIONS\",\n}\n\n/**\n * Shorthand constants for each HTTP method.\n *\n * These are equivalent to the corresponding enum members in `Method`.\n * For example, `GET === Method.GET`.\n */\nexport const { GET, PUT, HEAD, POST, PATCH, DELETE, OPTIONS } = Method;\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Time constants in seconds. Month is approximated as 30 days.\n */\nexport const Time = {\n Second: 1,\n Minute: 60,\n Hour: 3600, // 60 * 60\n Day: 86400, // 60 * 60 * 24\n Week: 604800, // 60 * 60 * 24 * 7\n Month: 2592000, // 60 * 60 * 24 * 30\n Year: 31536000, // 60 * 60 * 24 * 365\n} as const;\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Checks if the provided value is an array of strings.\n *\n * @param value - The value to check.\n * @returns True if `array` is an array where every item is a string.\n */\nexport function isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\n/**\n * Checks if a value is a string.\n *\n * @param value - The value to check.\n * @returns `true` if the value is a string, otherwise `false`.\n */\nexport function isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\n\n/**\n * Checks if a value is a valid number (not NaN).\n *\n * This function returns `true` if the value is of type `number`\n * and is not `NaN`. It works as a type guard for TypeScript.\n *\n * @param value - The value to check.\n * @returns `true` if the value is a number and not `NaN`, otherwise `false`.\n */\nexport function isNumber(value: unknown): value is number {\n return typeof value === \"number\" && !Number.isNaN(value);\n}\n\n/**\n * Checks if a value is a boolean.\n *\n * @param value - The value to check.\n * @returns `true` if the value is a boolean (`true` or `false`), otherwise `false`.\n */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === \"boolean\";\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CacheInit } from \"../interfaces/cache\";\n\nimport { isString } from \"./basic\";\n\n/**\n * Asserts that a value is a valid {@link CacheInit} object.\n *\n * Ensures that if provided, `name` is a string and `getKey` is a function.\n *\n * @param value - The value to check.\n * @throws TypeError If the object shape is invalid.\n */\nexport function assertCacheInit(value: unknown): asserts value is CacheInit {\n if (typeof value !== \"object\" || value === null) {\n throw new TypeError(\"CacheInit must be an object.\");\n }\n\n const { name, getKey } = value as Partial<CacheInit>;\n\n assertCacheName(name);\n assertGetKey(getKey);\n}\n\n/**\n * Asserts that a value is a string suitable for a cache name.\n *\n * If the value is `undefined`, this function does nothing.\n * Otherwise, it throws a `TypeError` if the value is not a string.\n *\n * @param value - The value to check.\n * @throws TypeError If the value is defined but not a string.\n */\nexport function assertCacheName(value: unknown): asserts value is string | undefined {\n if (value === undefined) return;\n if (!isString(value)) {\n throw new TypeError(\"Cache name must be a string.\");\n }\n}\n\n/**\n * Asserts that a value is a function suitable for `getKey`.\n *\n * If the value is `undefined`, this function does nothing.\n * Otherwise, it throws a `TypeError` if the value is not a function.\n *\n * @param value - The value to check.\n * @throws TypeError If the value is defined but not a function.\n */\nexport function assertGetKey(\n value: unknown,\n): asserts value is (request: Request) => URL | undefined {\n if (value === undefined) return;\n if (typeof value !== \"function\") {\n throw new TypeError(\"getKey must be a function.\");\n }\n}\n\n/**\n * Asserts that a value is a `URL` instance suitable for use as a cache key.\n *\n * If the value is not a `URL`, this function throws a `TypeError`.\n *\n * @param value - The value to check.\n * @throws TypeError If the value is not a `URL`.\n */\nexport function assertKey(value: unknown): asserts value is URL {\n if (!(value instanceof URL)) {\n throw new TypeError(\"getKey must return a URL.\");\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n *\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Returns a new URL with its query parameters sorted into a stable order.\n *\n * This is used for cache key generation: URLs that differ only in the\n * order of their query parameters will normalize to the same key.\n *\n * @param request - The incoming Request whose URL will be normalized.\n * @returns A new URL with query parameters sorted by name.\n */\nexport function sortSearchParams(request: Request): URL {\n const url = new URL(request.url);\n url.searchParams.sort();\n url.hash = \"\";\n return url;\n}\n\n/**\n * Returns a new URL with all query parameters removed.\n *\n * This is used when query parameters are not relevant to cache lookups,\n * ensuring that variants of the same resource share a single cache entry.\n *\n * @param request - The incoming Request whose URL will be normalized.\n * @returns A new URL with no query parameters.\n */\nexport function stripSearchParams(request: Request): URL {\n const url = new URL(request.url);\n url.search = \"\";\n url.hash = \"\";\n return url;\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Worker } from \"../../interfaces/worker\";\n\nimport { CacheRule } from \"./rules/interfaces\";\n\n/**\n * Represents a cache policy, defining the rules that determine\n * whether a cached response can be used.\n *\n * The `CachePolicy` executes its rules in order, passing the cached\n * response through a chain of validators. Each rule can:\n * - Return the cached response if eligible,\n * - Transform it (e.g., for `HEAD` requests), or\n * - Return `undefined` to indicate the cache cannot be used.\n *\n * The policy **does not fetch from origin**; it only evaluates the cache.\n */\nexport class CachePolicy {\n private readonly rules: CacheRule[] = [];\n\n /**\n * Adds one or more cache rules to the policy.\n *\n * @param rules - One or more `CacheRule` instances to apply.\n * @returns `this` for chaining.\n */\n public use(...rules: CacheRule[]): this {\n this.rules.push(...rules);\n return this;\n }\n\n /**\n * Executes the cache rules in order to determine cache eligibility.\n *\n * Each rule receives the cached response (or `undefined`) from the\n * next rule in the chain. If all rules pass, the cached response is returned.\n *\n * @param worker - The worker context containing the request.\n * @param getCached - Function returning the cached response if available.\n * @returns The cached response if allowed by all rules, or `undefined`\n * if the cache cannot be used.\n */\n public execute(\n worker: Worker,\n getCached: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n const chain = this.rules.reduceRight(\n (next, rule) => () => rule.apply(worker, next),\n () => getCached(),\n );\n return chain();\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n *\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StatusCodes } from \"http-status-codes\";\n\nimport { CacheControl, GET } from \"../../constants\";\nimport { HttpHeader } from \"../../constants/headers\";\nimport { lexCompare } from \"../../utils/compare\";\nimport { getHeaderValues } from \"../../utils/headers\";\n\n/** Base URL used for constructing cache keys. Only used internally. */\nconst VARY_CACHE_URL = \"https://vary\";\n\n/**\n * Wildcard member (`*`) for the `Vary` header.\n *\n * When present, it indicates that the response can vary based on unspecified\n * request headers. Such a response **MUST NOT be stored by a shared cache**,\n * since it cannot be reliably reused for any request.\n *\n * Example:\n * ```http\n * Vary: *\n * ```\n */\nconst VARY_WILDCARD = \"*\";\n\n/**\n * Determines whether a given Response is safe to cache.\n *\n * Internal utility used by the caching pipeline to decide if a response\n * should be stored in the cache. Returns `true` only if:\n *\n * - The response status is `200 OK`.\n * - The request method is `GET`.\n * - The response does not have a `Vary` header containing `*`.\n * - The response has TTL specified in max-age or s-maxage.\n * - Neither the request nor the response has `Cache-Control: no-store`.\n * - The response is not marked `private` and does not specify `max-age=0`.\n * - The request does **not** include sensitive headers such as `Authorization` or `Cookie`.\n * - The response does **not** include a `Set-Cookie` header.\n * - The response does not include a `Content-Range` header (partial content).\n *\n * These checks collectively ensure that the response is publicly cacheable,\n * consistent with Cloudflare's and general HTTP caching rules.\n *\n * @param request - The incoming Request object.\n * @param response - The Response object generated for the request.\n * @returns `true` if the response can safely be cached; `false` otherwise.\n * @throws Error If a 200 OK response contains a Content-Range header.\n */\nexport function isCacheable(request: Request, response: Response): boolean {\n if (response.status !== StatusCodes.OK) return false;\n if (request.method !== GET) return false;\n\n if (request.headers.has(HttpHeader.AUTHORIZATION)) return false;\n if (request.headers.has(HttpHeader.COOKIE)) return false;\n\n const requestCacheControl = getCacheControl(request.headers);\n if (requestCacheControl[\"no-store\"]) return false;\n\n if (!response.headers.has(HttpHeader.CACHE_CONTROL)) return false;\n const responseCacheControl = getCacheControl(response.headers);\n const ttl = responseCacheControl[\"s-maxage\"] ?? responseCacheControl[\"max-age\"];\n if (ttl === undefined || ttl === 0) return false;\n if (responseCacheControl[\"no-store\"]) return false;\n if (responseCacheControl[\"no-cache\"]) return false;\n if (responseCacheControl[\"private\"]) return false;\n\n if (response.headers.has(HttpHeader.SET_COOKIE)) return false;\n if (getVaryHeader(response).includes(VARY_WILDCARD)) return false;\n\n if (response.headers.has(HttpHeader.INTERNAL_VARIANT_SET)) {\n throw new Error(\"Found conflicting vary header.\");\n }\n\n if (response.headers.has(HttpHeader.CONTENT_RANGE)) {\n throw new Error(\"Found content-range header on 200 OK. Must use 206 Partial Content\");\n }\n\n return true;\n}\n\n/**\n * Parses the Cache-Control header from the given headers.\n *\n * @param headers - The request headers to inspect.\n * @returns A `CacheControl` object.\n */\nexport function getCacheControl(headers: Headers): CacheControl {\n return CacheControl.parse(headers.get(HttpHeader.CACHE_CONTROL) ?? \"\");\n}\n\n/**\n * Extracts and normalizes the `Vary` header from a Response.\n * - Splits comma-separated values\n * - Deduplicates\n * - Converts all values to lowercase\n * - Sorts lexicographically\n * - Removes `Vary` headers to ignore for caching.\n *\n * @param response The Response object containing headers.\n * @returns An array of normalized header names from the Vary header.\n */\nexport function getVaryHeader(response: Response): string[] {\n return getFilteredVary(getHeaderValues(response.headers, HttpHeader.VARY));\n}\n\n/**\n * Filters out headers that should be ignored for caching, currently:\n * - `Accept-Encoding` (handled automatically by the platform)\n *\n * @param vary Array of normalized Vary header names.\n * @returns Array of headers used for computing cache variations.\n */\nexport function getFilteredVary(vary: string[]): string[] {\n const values = vary\n .map((h) => h.toLowerCase())\n .filter((value) => value !== HttpHeader.ACCEPT_ENCODING)\n .sort(lexCompare);\n return Array.from(new Set(values));\n}\n\n/**\n * Generates a Vary-aware cache key for a request.\n *\n * The key is based on:\n * 1. The provided `key` URL, which is normalized by default but can be fully customized\n * by the caller. For example, users can:\n * - Sort query parameters\n * - Remove the search/query string entirely\n * - Exclude certain query parameters\n * This allows full control over how this cache key is generated.\n * 2. The request headers listed in `vary` (after filtering and lowercasing).\n *\n * Behavior:\n * - Headers in `vary` are sorted and included in the key.\n * - The combination of the key URL and header values is base64-encoded to produce\n * a safe cache key.\n * - The resulting string is returned as an absolute URL rooted at `VARY_CACHE_URL`.\n *\n * @param request The Request object used to generate the key.\n * @param vary Array of header names from the `Vary` header that affect caching.\n * @param key The cache key to be used for this request. Can be modified by the caller for\n * custom cache key behavior.\n * @returns A URL representing a unique cache key for this request + Vary headers.\n */\nexport function getVaryKey(request: Request, vary: string[], key: URL): string {\n const varyPairs: [string, string][] = [];\n const filtered = getFilteredVary(vary);\n\n for (const header of filtered) {\n const value = request.headers.get(header);\n if (value !== null) {\n varyPairs.push([header, normalizeVaryValue(header, value)]);\n }\n }\n\n const encoded = base64UrlEncode(JSON.stringify([key.toString(), varyPairs]));\n return new URL(encoded, VARY_CACHE_URL).toString();\n}\n\n/**\n * Normalizes the value of a header used in a Vary key.\n *\n * Only lowercases headers that are defined as case-insensitive\n * by HTTP standards and commonly used for content negotiation.\n *\n * @param name - The header name (case-insensitive).\n * @param value - The header value as received from the request.\n * @returns The normalized header value.\n */\nexport function normalizeVaryValue(name: string, value: string): string {\n switch (name.toLowerCase()) {\n case HttpHeader.ACCEPT:\n case HttpHeader.ACCEPT_LANGUAGE:\n case HttpHeader.ORIGIN:\n return value.toLowerCase();\n default:\n return value;\n }\n}\n\n/**\n * Encodes a string as URL-safe Base64.\n * - Converts to UTF-8 bytes\n * - Base64-encodes\n * - Replaces `+` with `-` and `/` with `_`\n * - Removes trailing `=`\n *\n * @param str The input string to encode.\n * @returns URL-safe Base64 string.\n */\nexport function base64UrlEncode(str: string): string {\n const utf8 = new TextEncoder().encode(str);\n let binary = \"\";\n for (const byte of utf8) {\n binary += String.fromCodePoint(byte);\n }\n return btoa(binary)\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/={1,2}$/, \"\");\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Internally used headers.\n */\nexport const HttpHeader = {\n ACCEPT: \"accept\",\n ACCEPT_ENCODING: \"accept-encoding\",\n ACCEPT_LANGUAGE: \"accept-language\",\n ACCEPT_RANGES: \"accept-ranges\",\n ALLOW: \"allow\",\n AUTHORIZATION: \"authorization\",\n CACHE_CONTROL: \"cache-control\",\n CONNECTION: \"connection\",\n CONTENT_DISPOSITION: \"content-disposition\",\n CONTENT_ENCODING: \"content-encoding\",\n CONTENT_LANGUAGE: \"content-language\",\n CONTENT_LENGTH: \"content-length\",\n CONTENT_RANGE: \"content-range\",\n CONTENT_TYPE: \"content-type\",\n CONTENT_MD5: \"content-md5\",\n COOKIE: \"cookie\",\n ETAG: \"etag\",\n IF_MATCH: \"if-match\",\n IF_MODIFIED_SINCE: \"if-modified-since\",\n IF_NONE_MATCH: \"if-none-match\",\n IF_UNMODIFIED_SINCE: \"if-unmodified-since\",\n LAST_MODIFIED: \"last-modified\",\n ORIGIN: \"origin\",\n RANGE: \"range\",\n SET_COOKIE: \"set-cookie\",\n VARY: \"vary\",\n\n // Cors Headers\n ACCESS_CONTROL_ALLOW_CREDENTIALS: \"access-control-allow-credentials\",\n ACCESS_CONTROL_ALLOW_HEADERS: \"access-control-allow-headers\",\n ACCESS_CONTROL_ALLOW_METHODS: \"access-control-allow-methods\",\n ACCESS_CONTROL_ALLOW_ORIGIN: \"access-control-allow-origin\",\n ACCESS_CONTROL_EXPOSE_HEADERS: \"access-control-expose-headers\",\n ACCESS_CONTROL_MAX_AGE: \"access-control-max-age\",\n\n // Websocket Headers\n SEC_WEBSOCKET_VERSION: \"sec-websocket-version\",\n UPGRADE: \"upgrade\",\n\n // Internal Headers\n INTERNAL_VARIANT_SET: \"internal-variant-set\",\n} as const;\n\n/**\n * Headers that must not be sent in 304 Not Modified responses.\n * These are stripped to comply with the HTTP spec.\n */\nexport const FORBIDDEN_304_HEADERS = [\n HttpHeader.CONTENT_TYPE,\n HttpHeader.CONTENT_LENGTH,\n HttpHeader.CONTENT_RANGE,\n HttpHeader.CONTENT_ENCODING,\n HttpHeader.CONTENT_LANGUAGE,\n HttpHeader.CONTENT_DISPOSITION,\n HttpHeader.CONTENT_MD5,\n];\n\n/**\n * Headers that should not be sent in 204 No Content responses.\n * Stripping them is recommended but optional per spec.\n */\nexport const FORBIDDEN_204_HEADERS = [HttpHeader.CONTENT_LENGTH, HttpHeader.CONTENT_RANGE];\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Lexicographically compares two strings.\n *\n * This comparator can be used in `Array.prototype.sort()` to produce a\n * consistent, stable ordering of string arrays.\n *\n * @param a - The first string to compare.\n * @param b - The second string to compare.\n * @returns A number indicating the relative order of `a` and `b`.\n */\nexport function lexCompare(a: string, b: string): number {\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lexCompare } from \"./compare\";\n\n/**\n * Sets a header on the given Headers object.\n *\n * - If `value` is an array, any duplicates and empty strings are removed.\n * - If the resulting value is empty, the header is deleted.\n * - Otherwise, values are joined with `\", \"` and set as the header value.\n *\n * @param headers - The Headers object to modify.\n * @param key - The header name to set.\n * @param value - The header value(s) to set. Can be a string or array of strings.\n */\nexport function setHeader(headers: Headers, key: string, value: string | string[]): void {\n const raw = Array.isArray(value) ? value : [value];\n const values = Array.from(new Set(raw.map((v) => v.trim())))\n .filter((v) => v.length)\n .sort(lexCompare);\n\n if (!values.length) {\n headers.delete(key);\n return;\n }\n\n headers.set(key, values.join(\", \"));\n}\n\n/**\n * Merges new value(s) into an existing header on the given Headers object.\n *\n * - Preserves any existing values and adds new ones.\n * - Removes duplicates and trims all values.\n * - If the header does not exist, it is created.\n * - If the resulting value array is empty, the header is deleted.\n *\n * @param headers - The Headers object to modify.\n * @param key - The header name to merge into.\n * @param value - The new header value(s) to add. Can be a string or array of strings.\n */\nexport function mergeHeader(headers: Headers, key: string, value: string | string[]): void {\n const values = Array.isArray(value) ? value : [value];\n if (values.length === 0) return;\n\n const existing = getHeaderValues(headers, key);\n const merged = existing.concat(values.map((v) => v.trim()));\n\n setHeader(headers, key, merged);\n}\n\n/**\n * Returns the values of an HTTP header as an array of strings.\n *\n * This helper:\n * - Retrieves the header value by `key`.\n * - Splits the value on commas.\n * - Trims surrounding whitespace from each entry.\n * - Filters out any empty tokens.\n * - Removes duplicate values (case-sensitive)\n *\n * If the header is not present, an empty array is returned.\n *\n */\nexport function getHeaderValues(headers: Headers, key: string): string[] {\n const values =\n headers\n .get(key)\n ?.split(\",\")\n .map((v) => v.trim())\n .filter((v) => v.length > 0) ?? [];\n return Array.from(new Set(values)).sort(lexCompare);\n}\n\n/**\n * Removes a list of header fields from a {@link Headers} object.\n *\n * @param headers - The {@link Headers} object to modify in place.\n * @param keys - An array of header field names to remove. Header names are\n * matched case-insensitively per the Fetch spec.\n */\nexport function filterHeaders(headers: Headers, keys: string[]): void {\n for (const key of keys) {\n headers.delete(key);\n }\n}\n\n/**\n * Extracts all header names from a `Headers` object, normalizes them,\n * and returns them in a stable, lexicographically sorted array.\n *\n * @param headers - The `Headers` object to extract keys from.\n * @returns A sorted array of lowercase header names.\n */\nexport function getHeaderKeys(headers: Headers): string[] {\n return [...headers.keys()].sort(lexCompare);\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../../constants/headers\";\nimport { isNumber, isString } from \"../../../guards/basic\";\nimport { getHeaderValues } from \"../../../utils/headers\";\n\nimport { ByteRange, CacheValidators } from \"./interfaces\";\n\nconst RANGE_REGEX = /^bytes=(\\d{1,12})-(\\d{0,12})$/;\nconst ETAG_WEAK_PREFIX = \"W/\";\nconst WILDCARD_ETAG = \"*\";\n\n/**\n * Parses the `Range` header from an HTTP request and returns a byte range object.\n *\n * Only supports **single-range headers** of the form `bytes=X-Y` or `bytes=X-`.\n * - `X` (start) is **required** and must be a whole number (up to 12 digits).\n * - `Y` (end) is optional; if missing, `end` is `undefined`.\n *\n * @param request - The HTTP request object containing headers.\n * @returns A `ByteRange` object with `start` and optional `end` if valid; otherwise `undefined`.\n */\nexport function getRange(request: Request): ByteRange | undefined {\n const range = request.headers.get(HttpHeader.RANGE);\n if (!range) return;\n\n const match = RANGE_REGEX.exec(range);\n if (!match) return;\n\n const start = Number(match[1]);\n const end = match[2] === \"\" ? undefined : Number(match[2]);\n\n return end === undefined ? { start } : { start, end };\n}\n\n/**\n * Evaluates an `If-Match` precondition against the current ETag.\n *\n * Returns `true` when the precondition fails, meaning the resource’s\n * current ETag does **not** match any of the supplied `If-Match` values\n * and the request should return **412 Precondition Failed**.\n *\n * @param ifMatch - Parsed `If-Match` header values.\n * @param etag - Current entity tag for the resource.\n * @returns `true` if the precondition fails; otherwise `false`.\n */\nexport function isPreconditionFailed(ifMatch: string[], etag: string): boolean {\n return ifMatch.length > 0 && !found(ifMatch, etag, WILDCARD_ETAG);\n}\n\n/**\n * Evaluates an `If-None-Match` precondition against the current ETag.\n *\n * Returns `true` when the resource has **not** been modified since the\n * validator was issued — i.e., when the normalized ETag matches one of\n * the `If-None-Match` values or the wildcard `\"*\"`.\n *\n * @param ifNoneMatch - Parsed `If-None-Match` header values.\n * @param etag - Current entity tag for the resource.\n * @returns `true` if the response should return **304 Not Modified**; otherwise `false`.\n */\nexport function isNotModified(ifNoneMatch: string[], etag: string): boolean {\n return found(ifNoneMatch, normalizeEtag(etag), WILDCARD_ETAG);\n}\n\n/**\n * Determines whether any of the given search values appear in the array.\n *\n * @param array - The array to search.\n * @param search - One or more values to look for.\n * @returns `true` if any search value is found in the array; otherwise `false`.\n */\nexport function found(array: string[], ...search: string[]): boolean {\n return array.some((value) => search.includes(value));\n}\n\n/**\n * Parses a date string into a timestamp (milliseconds since epoch).\n *\n * Returns `undefined` for invalid, null, or non-string values.\n *\n * @param value - The date string to parse.\n * @returns Parsed timestamp if valid; otherwise `undefined`.\n */\nexport function toDate(value: string | null | undefined): number | undefined {\n if (!isString(value)) return undefined;\n\n const date = Date.parse(value);\n return Number.isNaN(date) ? undefined : date;\n}\n\n/**\n * Normalizes an ETag for equality comparison.\n *\n * Weak ETags (`W/\"etag\"`) are converted to their strong form by removing\n * the leading `W/` prefix. Strong ETags are returned unchanged.\n *\n * @param etag - The entity tag to normalize.\n * @returns The normalized ETag string.\n */\nexport function normalizeEtag(etag: string): string {\n return etag.startsWith(ETAG_WEAK_PREFIX) ? etag.slice(2) : etag;\n}\n\n/**\n * Extracts cache validator headers from a request.\n *\n * Returns an object containing all standard conditional request headers:\n * - `If-Match` (weak validators removed)\n * - `If-None-Match` (normalized)\n * - `If-Modified-Since`\n * - `If-Unmodified-Since`\n *\n * @param headers - The headers object from which to extract validators.\n * @returns A `CacheValidators` structure containing parsed header values.\n */\nexport function getCacheValidators(headers: Headers): CacheValidators {\n return {\n ifMatch: getHeaderValues(headers, HttpHeader.IF_MATCH).filter(\n (value) => !value.startsWith(ETAG_WEAK_PREFIX),\n ),\n ifNoneMatch: getHeaderValues(headers, HttpHeader.IF_NONE_MATCH).map(normalizeEtag),\n ifModifiedSince: headers.get(HttpHeader.IF_MODIFIED_SINCE),\n ifUnmodifiedSince: headers.get(HttpHeader.IF_UNMODIFIED_SINCE),\n };\n}\n\n/**\n * Returns true if any cache validator headers are present.\n *\n * Useful as a quick check for conditional requests where the\n * specific values are not important.\n *\n * @param headers - The request headers to inspect.\n * @returns `true` if any validator exists; otherwise `false`.\n */\nexport function hasCacheValidator(headers: Headers): boolean {\n const { ifNoneMatch, ifMatch, ifModifiedSince, ifUnmodifiedSince } =\n getCacheValidators(headers);\n return (\n ifNoneMatch.length > 0 ||\n ifMatch.length > 0 ||\n ifModifiedSince !== null ||\n ifUnmodifiedSince !== null\n );\n}\n\n/**\n * Safely extracts the `Content-Length` header value.\n *\n * Returns the length as a number if present and valid. Returns `undefined`\n * if the header is missing, empty, or not a valid number.\n *\n * @param headers - The headers object to read from.\n * @returns Parsed content length if valid; otherwise `undefined`.\n */\nexport function getContentLength(headers: Headers): number | undefined {\n const lengthHeader = headers.get(HttpHeader.CONTENT_LENGTH);\n if (lengthHeader === null) return;\n if (lengthHeader.trim() === \"\") return;\n\n const length = Number(lengthHeader);\n if (!isNumber(length)) return;\n\n return length;\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Worker } from \"../../../interfaces\";\nimport { getCacheControl } from \"../utils\";\n\nimport { CacheRule } from \"./interfaces\";\nimport { hasCacheValidator } from \"./utils\";\n\n/**\n * Determines cache eligibility based on request `Cache-Control` headers.\n *\n * - `no-store` always prevents using the cache.\n * - `no-cache` or `max-age=0` prevent using the cache **unless** conditional validators\n * (e.g., `If-None-Match`, `If-Modified-Since`) are present.\n * - Otherwise, the request passes to the next rule in the chain.\n */\nexport class CacheControlRule implements CacheRule {\n /**\n * Applies cache-control header validation to determine cache usability.\n *\n * @param worker - The worker context containing the request.\n * @param next - Function invoking the next cache rule or returning a cached response.\n * @returns The cached response if allowed by cache-control, or `undefined` if the cache cannot be used.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n const cache = getCacheControl(worker.request.headers);\n\n if (cache[\"no-store\"]) {\n return undefined;\n }\n\n if (\n (cache[\"no-cache\"] || cache[\"max-age\"] === 0) &&\n !hasCacheValidator(worker.request.headers)\n ) {\n return undefined;\n }\n\n return next();\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getReasonPhrase, StatusCodes } from \"http-status-codes\";\n\nimport { CacheControl } from \"./constants/cache\";\nimport { HttpHeader } from \"./constants/headers\";\nimport { assertMethods } from \"./guards/methods\";\nimport { ErrorJson } from \"./interfaces/error\";\nimport { Worker } from \"./interfaces/worker\";\nimport { WS_VERSION } from \"./middleware/websocket/constants\";\nimport { JsonResponse } from \"./responses\";\n\n/**\n * Generic HTTP error response.\n * Sends a JSON body with status, error message, and details.\n */\nexport class HttpError extends JsonResponse {\n /**\n * @param worker The worker handling the request.\n * @param status HTTP status code.\n * @param details Optional detailed error message.\n */\n constructor(\n status: StatusCodes,\n protected readonly details?: string,\n ) {\n const json: ErrorJson = {\n status,\n error: getReasonPhrase(status),\n details: details ?? \"\",\n };\n super(json, CacheControl.DISABLE, status);\n }\n}\n\n/**\n * Creates a structured error response without exposing the error\n * details to the client. Links the sent response to the logged\n * error via a generated correlation ID.\n *\n * Status defaults to 500 Internal Server Error.\n */\nexport class LoggedHttpError extends HttpError {\n constructor(error: unknown, status: StatusCodes = StatusCodes.INTERNAL_SERVER_ERROR) {\n const uuid = crypto.randomUUID();\n console.error(uuid, error);\n super(status, uuid);\n }\n}\n\n/** 400 Bad Request error response. */\nexport class BadRequest extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.BAD_REQUEST, details);\n }\n}\n\n/** 401 Unauthorized error response. */\nexport class Unauthorized extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.UNAUTHORIZED, details);\n }\n}\n\n/** 403 Forbidden error response. */\nexport class Forbidden extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.FORBIDDEN, details);\n }\n}\n\n/** 404 Not Found error response. */\nexport class NotFound extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.NOT_FOUND, details);\n }\n}\n\n/** 405 Method Not Allowed error response. */\nexport class MethodNotAllowed extends HttpError {\n constructor(worker: Worker) {\n const methods = worker.getAllowedMethods();\n assertMethods(methods);\n\n super(StatusCodes.METHOD_NOT_ALLOWED, `${worker.request.method} method not allowed.`);\n this.setHeader(HttpHeader.ALLOW, methods);\n }\n}\n\n/** 412 Precondition Failed error response */\nexport class PreconditionFailed extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.PRECONDITION_FAILED, details);\n }\n}\n\n/** 426 Upgrade Required error response. */\nexport class UpgradeRequired extends HttpError {\n constructor() {\n super(StatusCodes.UPGRADE_REQUIRED);\n this.setHeader(HttpHeader.SEC_WEBSOCKET_VERSION, WS_VERSION);\n }\n}\n\n/** 500 Internal Server Error response. */\nexport class InternalServerError extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.INTERNAL_SERVER_ERROR, details);\n }\n}\n\n/** 501 Not Implemented error response. */\nexport class NotImplemented extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.NOT_IMPLEMENTED, details);\n }\n}\n\n/** 501 Method Not Implemented error response for unsupported HTTP methods. */\nexport class MethodNotImplemented extends NotImplemented {\n constructor(worker: Worker) {\n super(`${worker.request.method} method not implemented.`);\n }\n}\n\n/** 503 Service Unavailable error response. */\nexport class ServiceUnavailable extends HttpError {\n constructor(details?: string) {\n super(StatusCodes.SERVICE_UNAVAILABLE, details);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Method } from \"../constants/methods\";\n\nimport { isString } from \"./basic\";\n\n/**\n * A set containing all supported HTTP methods.\n *\n * Useful for runtime checks like validating request methods.\n */\nconst METHOD_SET: Set<string> = new Set(Object.values(Method));\n\n/**\n * Type guard that checks if a string is a valid HTTP method.\n *\n * @param value - The string to test.\n * @returns True if `value` is a recognized HTTP method.\n */\nexport function isMethod(value: unknown): value is Method {\n return isString(value) && METHOD_SET.has(value);\n}\n\n/**\n * Checks if a value is an array of valid HTTP methods.\n *\n * Each element is verified using the `isMethod` type guard.\n *\n * @param value - The value to check.\n * @returns `true` if `value` is an array and every element is a valid `Method`, otherwise `false`.\n */\nexport function isMethodArray(value: unknown): value is Method[] {\n return Array.isArray(value) && value.every(isMethod);\n}\n\n/**\n * Asserts that a value is an array of valid HTTP methods.\n *\n * This function uses {@link isMethodArray} to validate the input. If the\n * value is not an array of `Method` elements, it throws a `TypeError`.\n * Otherwise, TypeScript will narrow the type of `value` to `Method[]`\n * within the calling scope.\n *\n * @param value - The value to check.\n * @throws TypeError If `value` is not a valid {@link Method} array.\n */\nexport function assertMethods(value: unknown): asserts value is Method[] {\n if (!isMethodArray(value)) {\n const desc = Array.isArray(value) ? JSON.stringify(value) : String(value);\n throw new TypeError(`Invalid method array: ${desc}`);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** WebSocket upgrade header value */\nexport const WS_UPGRADE = \"upgrade\";\n\n/** WebSocket protocol header value */\nexport const WS_WEBSOCKET = \"websocket\";\n\n/** WebSocket protocol version */\nexport const WS_VERSION = \"13\";\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getReasonPhrase, StatusCodes } from \"http-status-codes\";\n\nimport { CacheControl } from \"./constants/cache\";\nimport { FORBIDDEN_204_HEADERS, FORBIDDEN_304_HEADERS, HttpHeader } from \"./constants/headers\";\nimport { MediaType, UTF8_CHARSET } from \"./constants/media\";\nimport { GET, HEAD } from \"./constants/methods\";\nimport { assertMethods } from \"./guards/methods\";\nimport { assertOctetStreamInit } from \"./guards/responses\";\nimport { Worker } from \"./interfaces\";\nimport { OctetStreamInit } from \"./interfaces/response\";\nimport { filterHeaders, mergeHeader, setHeader } from \"./utils/headers\";\nimport { withCharset } from \"./utils/media\";\n\n/**\n * Base class for building HTTP responses.\n * Manages headers, status, and media type.\n */\nabstract class BaseResponse {\n /** HTTP headers for the response. */\n public headers: Headers = new Headers();\n\n /** HTTP status code (default 200 OK). */\n public status: StatusCodes = StatusCodes.OK;\n\n /** Optional status text. Defaults to standard reason phrase. */\n public statusText?: string;\n\n /** Optional websocket property. */\n public webSocket?: WebSocket | null;\n\n /** Default media type of the response body. */\n public mediaType: string = withCharset(MediaType.PLAIN_TEXT, UTF8_CHARSET);\n\n /** Converts current state to ResponseInit for constructing a Response. */\n protected get responseInit(): ResponseInit {\n return {\n headers: this.headers,\n status: this.status,\n statusText: this.statusText ?? getReasonPhrase(this.status),\n webSocket: this.webSocket,\n encodeBody: \"automatic\",\n };\n }\n\n /** Sets a header, overwriting any existing value. */\n public setHeader(key: string, value: string | string[]): void {\n setHeader(this.headers, key, value);\n }\n\n /** Merges a header with existing values (does not overwrite). */\n public mergeHeader(key: string, value: string | string[]): void {\n mergeHeader(this.headers, key, value);\n }\n\n /** Adds a Content-Type header if not already existing (does not overwrite). */\n public addContentType() {\n if (!this.headers.get(HttpHeader.CONTENT_TYPE)) {\n this.setHeader(HttpHeader.CONTENT_TYPE, this.mediaType);\n }\n }\n\n /**\n * Removes headers that are disallowed or discouraged based on the current\n * status code.\n *\n * - **204 No Content:** strips headers that \"should not\" be sent\n * (`Content-Length`, `Content-Range`), per the HTTP spec.\n * - **304 Not Modified:** strips headers that \"must not\" be sent\n * (`Content-Type`, `Content-Length`, `Content-Range`, etc.), per the HTTP spec.\n *\n * This ensures that responses remain compliant with HTTP/1.1 standards while preserving\n * custom headers that are allowed.\n */\n public filterHeaders(): void {\n if (this.status === StatusCodes.NO_CONTENT) {\n filterHeaders(this.headers, FORBIDDEN_204_HEADERS);\n } else if (this.status === StatusCodes.NOT_MODIFIED) {\n filterHeaders(this.headers, FORBIDDEN_304_HEADERS);\n }\n }\n}\n\n/**\n * Base response class that adds caching headers.\n */\nabstract class CacheResponse extends BaseResponse {\n constructor(public cache?: CacheControl) {\n super();\n }\n\n /** Adds Cache-Control header if caching is configured. */\n protected addCacheHeader(): void {\n if (this.cache) {\n this.setHeader(HttpHeader.CACHE_CONTROL, CacheControl.stringify(this.cache));\n }\n }\n}\n\n/**\n * Core response. Combines caching, and content type headers.\n */\nexport abstract class WorkerResponse extends CacheResponse {\n constructor(\n private readonly body: BodyInit | null = null,\n cache?: CacheControl,\n ) {\n super(cache);\n }\n\n /** Builds the Response with body, headers, and status. */\n public async response(): Promise<Response> {\n this.addCacheHeader();\n\n const body = [StatusCodes.NO_CONTENT, StatusCodes.NOT_MODIFIED].includes(this.status)\n ? null\n : this.body;\n\n if (body) this.addContentType();\n\n this.filterHeaders();\n\n return new Response(body, this.responseInit);\n }\n}\n\n/**\n * Copies an existing response for mutation. Pass in a CacheControl\n * to be used for the response, overriding any existing `cache-control`\n * on the source response.\n */\nexport class CopyResponse extends WorkerResponse {\n constructor(response: Response, cache?: CacheControl) {\n super(response.body, cache);\n this.status = response.status;\n this.statusText = response.statusText;\n this.headers = new Headers(response.headers);\n }\n}\n\n/**\n * Copies the response, but with null body and status 304 Not Modified.\n */\nexport class NotModified extends WorkerResponse {\n constructor(response: Response) {\n super();\n this.status = StatusCodes.NOT_MODIFIED;\n this.headers = new Headers(response.headers);\n }\n}\n\n/**\n * Represents a successful response with customizable body, cache and status.\n */\nexport class SuccessResponse extends WorkerResponse {\n constructor(\n body: BodyInit | null = null,\n cache?: CacheControl,\n status: StatusCodes = StatusCodes.OK,\n ) {\n super(body, cache);\n this.status = status;\n }\n}\n\n/**\n * JSON response. Automatically sets Content-Type to application/json.\n */\nexport class JsonResponse extends SuccessResponse {\n constructor(json: unknown = {}, cache?: CacheControl, status: StatusCodes = StatusCodes.OK) {\n super(JSON.stringify(json), cache, status);\n this.mediaType = withCharset(MediaType.JSON, UTF8_CHARSET);\n }\n}\n\n/**\n * HTML response. Automatically sets Content-Type to text/html.\n */\nexport class HtmlResponse extends SuccessResponse {\n constructor(\n body: string,\n cache?: CacheControl,\n status: StatusCodes = StatusCodes.OK,\n charset: string = UTF8_CHARSET,\n ) {\n super(body, cache, status);\n this.mediaType = withCharset(MediaType.HTML, charset);\n }\n}\n\n/**\n * Plain text response. Automatically sets Content-Type to text/plain.\n */\nexport class TextResponse extends SuccessResponse {\n constructor(\n body: string,\n cache?: CacheControl,\n status: StatusCodes = StatusCodes.OK,\n charset: string = UTF8_CHARSET,\n ) {\n super(body, cache, status);\n this.mediaType = withCharset(MediaType.PLAIN_TEXT, charset);\n }\n}\n\n/**\n * Represents an HTTP response for serving binary data as `application/octet-stream`.\n *\n * This class wraps a `ReadableStream` and sets all necessary headers for both\n * full and partial content responses, handling range requests in a hybrid way\n * to maximize browser and CDN caching.\n *\n * Key behaviors:\n * - `Content-Type` is set to `application/octet-stream`.\n * - `Accept-Ranges: bytes` is always included.\n * - `Content-Length` is always set to the validated length of the response body.\n * - If the request is a true partial range (offset > 0 or length < size), the response\n * will be `206 Partial Content` with the appropriate `Content-Range` header.\n * - If the requested range covers the entire file (even if a Range header is present),\n * the response will return `200 OK` to enable browser and edge caching.\n * - Zero-length streams (`size = 0`) are never treated as partial.\n * - Special case: a requested range of `0-0` on a non-empty file is normalized to 1 byte.\n */\nexport class OctetStream extends WorkerResponse {\n constructor(stream: ReadableStream, init: OctetStreamInit, cache?: CacheControl) {\n assertOctetStreamInit(init);\n\n super(stream, cache);\n this.mediaType = MediaType.OCTET_STREAM;\n\n const normalized = OctetStream.normalizeInit(init);\n const { size, offset, length } = normalized;\n\n if (OctetStream.isPartial(normalized)) {\n this.setHeader(\n HttpHeader.CONTENT_RANGE,\n `bytes ${offset}-${offset + length - 1}/${size}`,\n );\n this.status = StatusCodes.PARTIAL_CONTENT;\n }\n\n this.setHeader(HttpHeader.ACCEPT_RANGES, \"bytes\");\n this.setHeader(HttpHeader.CONTENT_LENGTH, `${length}`);\n }\n\n /**\n * Normalizes a partially-specified `OctetStreamInit` into a fully-specified object.\n *\n * Ensures that all required fields (`size`, `offset`, `length`) are defined:\n * - `offset` defaults to 0 if not provided.\n * - `length` defaults to `size - offset` if not provided.\n * - Special case: if `offset` and `length` are both 0 but `size > 0`, `length` is set to 1\n * to avoid zero-length partial streams.\n *\n * @param init - The initial `OctetStreamInit` object, possibly with missing `offset` or `length`.\n * @returns A fully-specified `OctetStreamInit` object with `size`, `offset`, and `length` guaranteed.\n */\n private static normalizeInit(init: OctetStreamInit): Required<OctetStreamInit> {\n const { size } = init;\n const offset = init.offset ?? 0;\n let length = init.length ?? size - offset;\n\n if (offset === 0 && length === 0 && size > 0) {\n length = 1;\n }\n\n return { size, offset, length };\n }\n\n /**\n * Determines whether the given `OctetStreamInit` represents a partial range.\n *\n * Partial ranges are defined as any range that does **not** cover the entire file:\n * - If `size === 0`, the stream is never partial.\n * - If `offset === 0` and `length === size`, the stream is treated as a full file (not partial),\n * even if a Range header is present. This enables browser and CDN caching.\n * - All other cases are considered partial, and will result in a `206 Partial Content` response.\n *\n * @param init - A fully-normalized `OctetStreamInit` object.\n * @returns `true` if the stream represents a partial range; `false` if it represents the full file.\n */\n private static isPartial(init: Required<OctetStreamInit>): boolean {\n if (init.size === 0) return false;\n return !(init.offset === 0 && init.length === init.size);\n }\n}\n\n/**\n * A streaming response for Cloudflare R2 objects.\n *\n * **Partial content support:** To enable HTTP 206 streaming, you must provide\n * request headers containing the `Range` header when calling the R2 bucket's `get()` method.\n *\n * Example:\n * ```ts\n * const stream = await this.env.R2_BUCKET.get(\"key\", { range: this.request.headers });\n * ```\n *\n * @param source - The R2 object to stream.\n * @param cache - Optional caching override.\n */\nexport class R2ObjectStream extends OctetStream {\n constructor(source: R2ObjectBody, cache?: CacheControl) {\n let useCache = cache;\n if (!useCache && source.httpMetadata?.cacheControl) {\n useCache = CacheControl.parse(source.httpMetadata.cacheControl);\n }\n\n super(source.body, R2ObjectStream.computeRange(source.size, source.range), useCache);\n\n this.setHeader(HttpHeader.ETAG, source.httpEtag);\n\n if (source.httpMetadata?.contentType) {\n this.mediaType = source.httpMetadata.contentType;\n }\n }\n\n /**\n * Computes an `OctetStreamInit` object from a given R2 range.\n *\n * This function normalizes a Cloudflare R2 `R2Range` into the shape expected\n * by `OctetStream`. It handles the following cases:\n *\n * - No range provided: returns `{ size }` (full content).\n * - `suffix` range: calculates the offset and length from the end of the file.\n * - Explicit `offset` and/or `length`: passed through as-is.\n *\n * @param size - The total size of the file/object.\n * @param range - Optional range to extract (from R2). Can be:\n * - `{ offset: number; length?: number }`\n * - `{ offset?: number; length: number }`\n * - `{ suffix: number }`\n * @returns An `OctetStreamInit` object suitable for `OctetStream`.\n */\n private static computeRange(size: number, range?: R2Range): OctetStreamInit {\n if (!range) return { size };\n\n if (\"suffix\" in range) {\n const offset = Math.max(0, size - range.suffix);\n const length = size - offset;\n return { size, offset, length };\n }\n\n return { size, ...range };\n }\n}\n\n/**\n * Response for WebSocket upgrade requests.\n * Automatically sets status to 101 and attaches the client socket.\n */\nexport class WebSocketUpgrade extends WorkerResponse {\n constructor(client: WebSocket) {\n super();\n this.status = StatusCodes.SWITCHING_PROTOCOLS;\n this.webSocket = client;\n }\n}\n\n/**\n * Response for `HEAD` requests. Copy headers and status from a `GET` response\n * without the body.\n */\nexport class Head extends WorkerResponse {\n constructor(get: Response) {\n super();\n this.status = get.status;\n this.statusText = get.statusText;\n this.headers = new Headers(get.headers);\n }\n}\n\n/**\n * Response for `OPTIONS` requests.\n */\nexport class Options extends WorkerResponse {\n constructor(worker: Worker) {\n const allowed = Array.from(new Set([GET, HEAD, ...worker.getAllowedMethods()]));\n assertMethods(allowed);\n\n super();\n this.status = StatusCodes.NO_CONTENT;\n this.setHeader(HttpHeader.ALLOW, allowed);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport const UTF8_CHARSET = \"utf-8\";\n\n/**\n * Internal media types.\n */\nexport enum MediaType {\n PLAIN_TEXT = \"text/plain\",\n HTML = \"text/html\",\n JSON = \"application/json\",\n OCTET_STREAM = \"application/octet-stream\",\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OctetStreamInit } from \"../interfaces\";\n\nimport { isNumber } from \"./basic\";\n\n/**\n * Asserts that a given value is a valid `OctetStreamInit` object.\n *\n * Properties:\n * - `size` (required): must be a non-negative number.\n * - `offset` (optional): if provided, must be a number between 0 and `size`.\n * - `length` (optional): if provided, must be a non-negative number such that `offset + length <= size`.\n *\n * If `offset` or `length` are `undefined`, they are considered as `0` and `size` respectively.\n *\n * Throws an error if validation fails.\n *\n * Acts as a TypeScript type predicate, so after calling it, `value` is narrowed to `OctetStreamInit`.\n *\n * @param value - The value to validate as `OctetStreamInit`.\n * @throws TypeError If the value is not a non-null object.\n * @throws RangeError If `size`, `offset`, or `length` are invalid.\n */\nexport function assertOctetStreamInit(value: unknown): asserts value is OctetStreamInit {\n if (typeof value !== \"object\" || value === null) {\n throw new TypeError(\"OctetStreamInit must be an object.\");\n }\n\n const obj = value as Record<string, unknown>;\n\n // size\n const size = obj[\"size\"];\n if (!isNumber(size) || size < 0 || !Number.isInteger(size)) {\n throw new RangeError(\n `OctetStreamInit.size must be a non-negative integer (size=${JSON.stringify(size)}).`,\n );\n }\n\n // offset\n const offset = obj[\"offset\"] ?? 0;\n if (!isNumber(offset) || offset < 0 || offset > size || !Number.isInteger(offset)) {\n throw new RangeError(\n `OctetStreamInit.offset must be a non-negative integer less than or equal to size (size=${JSON.stringify(size)}, offset=${JSON.stringify(offset)}).`,\n );\n }\n\n // length\n const length = obj[\"length\"] ?? size - offset;\n if (!isNumber(length) || length < 0 || offset + length > size || !Number.isInteger(length)) {\n throw new RangeError(\n `OctetStreamInit.length must be a non-negative integer less than or equal to size - offset (size=${JSON.stringify(size)}, offset=${JSON.stringify(offset)}, length=${JSON.stringify(length)}).`,\n );\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Appends a charset parameter to a given media type string,\n * avoiding duplicates and ignoring empty charsets.\n *\n * @param {string} mediaType - The MIME type (e.g., \"text/html\").\n * @param {string} charset - The character set to append (e.g., \"utf-8\").\n * @returns {string} The media type with charset appended if provided.\n */\nexport function withCharset(mediaType: string, charset: string): string {\n if (!charset || mediaType.toLowerCase().includes(\"charset=\")) {\n return mediaType;\n }\n return `${mediaType}; charset=${charset.toLowerCase()}`;\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StatusCodes } from \"http-status-codes\";\n\nimport { Worker } from \"../../../interfaces\";\n\nimport { CacheRule, CacheValidators } from \"./interfaces\";\nimport { getCacheValidators } from \"./utils\";\n\n/**\n * Base class for cache validation rules.\n *\n * `ValidationRule` provides a standard mechanism for inspecting an outgoing response,\n * extracting a specific header (such as `ETag` or `Last-Modified`),\n * and applying cache validators from the incoming request.\n *\n * Subclasses implement:\n * - `getHeader()` to extract the relevant header from the response.\n * - `response()` to decide whether to return the response as-is,\n * replace it with a conditional response (e.g., `304 Not Modified`),\n * or return `undefined` to indicate the cached response is invalid.\n *\n * Rules derived from this class are typically used to implement\n * conditional GET handling and other validation-aware caching logic.\n *\n * @template H - The type of header value extracted from the response (e.g., `string` or `Date`).\n */\nexport abstract class ValidationRule<H> implements CacheRule {\n /**\n * Extracts the target header value from a response.\n *\n * Implementations should return `undefined` if the header is missing or invalid.\n *\n * @param response - The response to inspect.\n * @returns The parsed header value, or `undefined` if unavailable.\n */\n protected abstract getHeader(response: Response): H | undefined;\n\n /**\n * Applies cache validation logic using the extracted header and request validators.\n *\n * Implementations determine whether the response is still valid or requires revalidation.\n * Returning `undefined` signals that the cached response cannot be used.\n *\n * @param response - The original response from the cache or origin.\n * @param header - The extracted header value relevant to validation.\n * @param validators - Parsed conditional headers from the incoming request.\n * @returns A `Response` if valid, or `undefined` if the cache entry is invalid.\n */\n protected abstract response(\n response: Response,\n header: H,\n validators: CacheValidators,\n ): Promise<Response | undefined>;\n\n /**\n * Core entry point for cache validation rules.\n *\n * Executes the next handler in the chain, inspects the resulting response,\n * and applies subclass-specific validation logic if appropriate.\n *\n * @param worker - The worker context for the current request.\n * @param next - A function that invokes the next rule or final handler.\n * @returns A validated `Response`, or `undefined` if validation fails.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n const response = await next();\n if (!response || response.status !== StatusCodes.OK) return response;\n\n const header = this.getHeader(response);\n if (header === undefined) return response;\n\n const validators = getCacheValidators(worker.request.headers);\n return this.response(response, header, validators);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../../constants/headers\";\nimport { PreconditionFailed } from \"../../../errors\";\nimport { NotModified } from \"../../../responses\";\n\nimport { CacheValidators } from \"./interfaces\";\nimport { isNotModified, isPreconditionFailed } from \"./utils\";\nimport { ValidationRule } from \"./validation\";\n\n/**\n * Base class for ETag-based cache validation rules.\n *\n * `MatchRule` specializes `ValidationRule` to handle `ETag` headers.\n * It extracts the `ETag` from the response and delegates validation\n * to subclasses via the `response()` method.\n *\n * Subclasses implement the behavior for specific conditional requests,\n * such as `If-Match` or `If-None-Match`.\n */\nabstract class MatchRule extends ValidationRule<string> {\n /**\n * Extracts the `ETag` header from a response.\n *\n * @param response - The response to inspect.\n * @returns The ETag string if present; otherwise `undefined`.\n */\n protected override getHeader(response: Response): string | undefined {\n return response.headers.get(HttpHeader.ETAG) ?? undefined;\n }\n}\n\n/**\n * Implements the `If-Match` conditional request validation.\n *\n * If the `If-Match` header is present and the response’s ETag does\n * not match any of the listed values, the rule returns a\n * `412 Precondition Failed` response.\n *\n * Otherwise, the original response is returned unchanged.\n */\nexport class IfMatchRule extends MatchRule {\n /**\n * Applies `If-Match` validation against the response’s ETag.\n *\n * @param response - The original response from cache.\n * @param etag - The ETag extracted from the response.\n * @param validators - Parsed cache validators from the request.\n * @returns A `Response` with `412 Precondition Failed` if validation fails,\n * or the original response if the precondition passes.\n */\n protected async response(\n response: Response,\n etag: string,\n validators: CacheValidators,\n ): Promise<Response | undefined> {\n if (isPreconditionFailed(validators.ifMatch, etag)) {\n return new PreconditionFailed(`ETag: ${etag}`).response();\n }\n\n return response;\n }\n}\n\n/**\n * Implements the `If-None-Match` conditional request validation.\n *\n * If the `If-None-Match` header is present and the response’s ETag matches\n * one of the listed values, the rule returns a `304 Not Modified` response.\n *\n * If `If-None-Match` is present but does not match, the cache entry\n * is considered invalid and `undefined` is returned.\n */\nexport class IfNoneMatchRule extends MatchRule {\n /**\n * Applies `If-None-Match` validation against the response’s ETag.\n *\n * @param response - The original response from cache.\n * @param etag - The ETag extracted from the response.\n * @param validators - Parsed cache validators from the request.\n * @returns A `304 Not Modified` response if the resource is unmodified,\n * the original response if no validators are present,\n * or `undefined` if validation fails and the cache should not be used.\n */\n protected async response(\n response: Response,\n etag: string,\n validators: CacheValidators,\n ): Promise<Response | undefined> {\n if (validators.ifNoneMatch.length === 0) return response;\n\n if (isNotModified(validators.ifNoneMatch, etag)) {\n return new NotModified(response).response();\n }\n\n return undefined;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GET, HEAD } from \"../../../constants\";\nimport { Worker } from \"../../../interfaces\";\nimport { Head } from \"../../../responses\";\n\nimport { CacheRule } from \"./interfaces\";\n\n/**\n * Determines cache eligibility based on the HTTP method.\n *\n * - `GET` requests pass the cached response to the next rule in the chain.\n * - `HEAD` requests convert a cached `GET` response into a `HEAD` response\n * before passing it along.\n * - All other methods bypass the cache and return `undefined`.\n */\nexport class MethodRule implements CacheRule {\n /**\n * Applies method-based cache validation.\n *\n * @param worker - The worker context containing the request.\n * @param next - Function invoking the next cache rule or returning a cached response.\n * @returns The cached response if eligible, a transformed `HEAD` response,\n * or `undefined` if the cache cannot be used.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n if (worker.request.method === GET) {\n return next();\n }\n\n if (worker.request.method === HEAD) {\n const response = await next();\n if (!response) return undefined;\n\n return new Head(response).response();\n }\n\n return undefined;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../../constants/headers\";\nimport { PreconditionFailed } from \"../../../errors\";\nimport { NotModified } from \"../../../responses\";\n\nimport { CacheValidators } from \"./interfaces\";\nimport { toDate } from \"./utils\";\nimport { ValidationRule } from \"./validation\";\n\n/**\n * Base class for `Last-Modified` header cache validation rules.\n *\n * `LastModifiedRule` specializes `ValidationRule` to handle the\n * `Last-Modified` header. It converts the header value into a\n * timestamp and delegates validation logic to subclasses.\n *\n * Subclasses implement behavior for conditional requests such as\n * `If-Modified-Since` and `If-Unmodified-Since`.\n */\nabstract class LastModifiedRule extends ValidationRule<number> {\n /**\n * Extracts and parses the `Last-Modified` header from a response.\n *\n * @param response - The response to inspect.\n * @returns The timestamp in milliseconds since epoch, or `undefined` if unavailable.\n */\n protected override getHeader(response: Response): number | undefined {\n return toDate(response.headers.get(HttpHeader.LAST_MODIFIED));\n }\n}\n\n/**\n * Implements the `If-Modified-Since` conditional request validation.\n *\n * If the resource has not been modified since the specified timestamp,\n * the rule returns a `304 Not Modified` response.\n *\n * Otherwise, `undefined` is returned to indicate the cache entry\n * cannot be used.\n */\nexport class ModifiedSinceRule extends LastModifiedRule {\n /**\n * Applies `If-Modified-Since` validation against the response’s `Last-Modified` value.\n *\n * @param response - The original response from cache.\n * @param lastModified - Timestamp of the resource’s last modification.\n * @param validators - Parsed cache validators from the request.\n * @returns A `304 Not Modified` response if the resource is unmodified,\n * the original response if no validator is present,\n * or `undefined` if the cache should not be used.\n */\n protected async response(\n response: Response,\n lastModified: number,\n validators: CacheValidators,\n ): Promise<Response | undefined> {\n const modifiedSince = toDate(validators.ifModifiedSince);\n if (modifiedSince === undefined) return response;\n\n if (lastModified <= modifiedSince) return new NotModified(response).response();\n\n return undefined;\n }\n}\n\n/**\n * Implements the `If-Unmodified-Since` conditional request validation.\n *\n * If the resource has been modified after the specified timestamp,\n * the rule returns a `412 Precondition Failed` response.\n *\n * Otherwise, the original response is returned.\n */\nexport class UnmodifiedSinceRule extends LastModifiedRule {\n /**\n * Applies `If-Unmodified-Since` validation against the response’s `Last-Modified` value.\n *\n * @param response - The original response from cache.\n * @param lastModified - Timestamp of the resource’s last modification.\n * @param validators - Parsed cache validators from the request.\n * @returns A `412 Precondition Failed` response if the resource was modified\n * after the specified timestamp, or the original response if valid.\n */\n protected async response(\n response: Response,\n lastModified: number,\n validators: CacheValidators,\n ): Promise<Response | undefined> {\n const unmodifiedSince = toDate(validators.ifUnmodifiedSince);\n if (unmodifiedSince === undefined) return response;\n\n if (lastModified > unmodifiedSince) {\n return new PreconditionFailed(\n `Last-Modified: ${new Date(lastModified).toUTCString()}`,\n ).response();\n }\n\n return response;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StatusCodes } from \"http-status-codes\";\n\nimport { Worker } from \"../../../interfaces/worker\";\n\nimport { CacheRule } from \"./interfaces\";\nimport { getContentLength, getRange } from \"./utils\";\n\n/**\n * Ensures cached responses can satisfy requests with a `Range` header.\n *\n * - Only full or full-from-start ranges are eligible.\n * - Requests with non-zero `start`, zero `end`, or mismatched `end` values\n * bypass the cache (`undefined` is returned).\n * - Requests without a `Range` header, or with open-ended ranges from 0,\n * pass the cached response to the next rule in the chain.\n */\nexport class RangeRule implements CacheRule {\n /**\n * Applies range-based cache validation.\n *\n * @param worker - The worker context containing the request.\n * @param next - Function invoking the next cache rule or returning a cached response.\n * @returns The cached response if it satisfies the requested range,\n * or `undefined` if the cache cannot be used.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n const range = getRange(worker.request);\n\n if (range && (range.start !== 0 || range.end === 0)) {\n return undefined;\n }\n\n const response = await next();\n if (!response || response.status !== StatusCodes.OK) return response;\n\n if (!range) return response;\n if (range.end === undefined) return response;\n\n const length = getContentLength(response.headers);\n if (!length) return undefined;\n if (range.end !== length - 1) return undefined;\n\n return response;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../../constants/headers\";\nimport { Worker } from \"../../../interfaces\";\n\nimport { CacheRule } from \"./interfaces\";\n\n/**\n * Prevents using the cache for requests that contain sensitive headers.\n *\n * - Requests with `Authorization` or `Cookie` headers bypass the cache.\n * - Otherwise, the request passes to the next rule in the chain.\n */\nexport class SecurityRule implements CacheRule {\n /**\n * Applies security-based cache validation.\n *\n * @param worker - The worker context containing the request.\n * @param next - Function invoking the next cache rule or returning a cached response.\n * @returns The cached response if allowed, or `undefined` if the cache cannot be used.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n const headers = worker.request.headers;\n if (headers.has(HttpHeader.AUTHORIZATION)) {\n return undefined;\n }\n if (headers.has(HttpHeader.COOKIE)) {\n return undefined;\n }\n\n return next();\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../../constants/headers\";\nimport { Worker } from \"../../../interfaces\";\n\nimport { CacheRule } from \"./interfaces\";\n\n/**\n * Prevents using the cache for requests that include the `Upgrade` header.\n *\n * - If the `Upgrade` header is present, the cache is bypassed.\n * - Otherwise, the request passes to the next rule in the chain.\n */\nexport class UpgradeRule implements CacheRule {\n /**\n * Applies the upgrade-header validation.\n *\n * @param worker - The worker context containing the request.\n * @param next - Function invoking the next cache rule or returning a cached response.\n * @returns The cached response if allowed, or `undefined` if the cache cannot be used.\n */\n public async apply(\n worker: Worker,\n next: () => Promise<Response | undefined>,\n ): Promise<Response | undefined> {\n if (worker.request.headers.has(HttpHeader.UPGRADE)) {\n return undefined;\n }\n\n return next();\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CacheControl } from \"../../constants\";\nimport { HttpHeader } from \"../../constants/headers\";\nimport { WorkerResponse } from \"../../responses\";\nimport { getHeaderValues } from \"../../utils/headers\";\n\nimport { getCacheControl, getFilteredVary } from \"./utils\";\n\n/**\n * Represents a Vary-aware cached response.\n *\n * Extends WorkerResponse to track which request headers affect the cached\n * response (Vary headers) and ensure correct TTL handling.\n *\n * This class is used internally in the caching system to:\n * - Store responses with full awareness of Vary headers.\n * - Append new Vary headers safely.\n * - Update TTLs to match incoming origin responses.\n * - Track whether the cached variant has been modified.\n */\nexport class VariantResponse extends WorkerResponse {\n private _isModified = false;\n\n private constructor(vary: string[]) {\n const filtered = getFilteredVary(vary);\n if (filtered.length === 0) {\n throw new Error(\"The filtered vary array is empty.\");\n }\n\n super();\n this.setHeader(HttpHeader.INTERNAL_VARIANT_SET, filtered);\n }\n\n /**\n * Creates a new VariantResponse with the specified Vary headers.\n *\n * @param vary - Array of request headers this response varies on.\n * @returns A new VariantResponse instance.\n */\n public static new(vary: string[]): VariantResponse {\n return new VariantResponse(getFilteredVary(vary));\n }\n\n /**\n * Restores a VariantResponse from an existing Response.\n *\n * @param source - The cached Response to restore.\n * @throws If the source response is not a variant response.\n * @returns A VariantResponse instance containing the original Vary headers and cache control.\n */\n public static restore(source: Response): VariantResponse {\n if (!VariantResponse.isVariantResponse(source)) {\n throw new Error(\"The source response is not a variant response\");\n }\n\n const variant = VariantResponse.new(\n getHeaderValues(source.headers, HttpHeader.INTERNAL_VARIANT_SET),\n );\n\n const cacheControl = source.headers.get(HttpHeader.CACHE_CONTROL);\n if (cacheControl) variant.cache = CacheControl.parse(cacheControl);\n\n return variant;\n }\n\n /**\n * Returns the Vary headers tracked by this response.\n */\n public get vary(): string[] {\n return getHeaderValues(this.headers, HttpHeader.INTERNAL_VARIANT_SET);\n }\n\n /**\n * Indicates whether the variant has been modified since creation or restoration.\n */\n public get isModified(): boolean {\n return this._isModified;\n }\n\n /**\n * Appends additional Vary headers to this response.\n *\n * Updates the internal _isModified flag if new headers are added.\n *\n * @param vary - Array of headers to merge into the existing Vary set.\n */\n public append(vary: string[]): void {\n const before = this.vary.length;\n this.mergeHeader(HttpHeader.INTERNAL_VARIANT_SET, getFilteredVary(vary));\n this._isModified = this.vary.length !== before;\n }\n\n /**\n * Determines if a response is a VariantResponse.\n *\n * @param response - The Response object to inspect.\n * @returns `true` if the response is a variant; otherwise `false`.\n */\n public static isVariantResponse(response: Response): boolean {\n return response.headers.has(HttpHeader.INTERNAL_VARIANT_SET);\n }\n\n /**\n * Updates this variant’s TTL to ensure it does not expire before\n * the TTL of the given origin response.\n *\n * Only modifies the TTL if the origin response explicitly provides\n * s-maxage or max-age. Updates _isModified if the TTL increases.\n *\n * @param response - The origin Response whose TTL should be considered.\n */\n public expireAfter(response: Response): void {\n const incoming = getCacheControl(response.headers);\n\n const incomingTTL = incoming[\"s-maxage\"] ?? incoming[\"max-age\"];\n if (incomingTTL === undefined) return;\n\n const currentTTL = this.cache?.[\"s-maxage\"];\n\n if (currentTTL === undefined || incomingTTL > currentTTL) {\n this.cache = {\n \"s-maxage\": incomingTTL,\n };\n this._isModified = true;\n }\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { assertKey } from \"../../guards/cache\";\nimport { CacheInit } from \"../../interfaces/cache\";\nimport { Middleware } from \"../../interfaces/middleware\";\nimport { Worker } from \"../../interfaces/worker\";\n\nimport { sortSearchParams } from \"./keys\";\nimport { CachePolicy } from \"./policy\";\nimport { CacheControlRule } from \"./rules/control\";\nimport { IfMatchRule, IfNoneMatchRule } from \"./rules/etag\";\nimport { MethodRule } from \"./rules/method\";\nimport { ModifiedSinceRule, UnmodifiedSinceRule } from \"./rules/modified\";\nimport { RangeRule } from \"./rules/range\";\nimport { SecurityRule } from \"./rules/security\";\nimport { UpgradeRule } from \"./rules/upgrade\";\nimport { getVaryHeader, getVaryKey, isCacheable } from \"./utils\";\nimport { VariantResponse } from \"./variant\";\n\n/**\n * Cache Middleware Implementation\n */\nexport class CacheHandler implements Middleware {\n private readonly init: CacheInit;\n\n constructor(init: CacheInit) {\n const { name, getKey = sortSearchParams } = init;\n\n this.init = {\n name: name?.trim() || undefined,\n getKey,\n };\n }\n\n /**\n * Handles an incoming request through the cache middleware.\n *\n * Behavior:\n * - Opens the configured cache (or the default cache if none specified).\n * - Creates a `CachePolicy` with default rules (GET check, range check, ETag handling).\n * - Executes the policy to determine if a cached response can be used.\n * - If a cached response is found and valid per the rules, it is returned.\n * - If no cached response is usable, the `next()` handler is invoked to fetch a fresh response.\n * - Stores the fresh response in the cache if it is cacheable.\n *\n * Note: The cache policy only checks if an existing cached response is usable.\n * It does not store the response; storage is handled later in `setCached()`.\n *\n * @param worker - The Worker instance containing the request and context.\n * @param next - Function to invoke the next middleware or origin fetch.\n * @returns A `Response` object, either from cache or freshly fetched.\n */\n public async handle(worker: Worker, next: () => Promise<Response>): Promise<Response> {\n const cache = this.init.name ? await caches.open(this.init.name) : caches.default;\n\n const policy = new CachePolicy()\n .use(new CacheControlRule())\n .use(new MethodRule())\n .use(new UpgradeRule())\n .use(new SecurityRule())\n .use(new RangeRule())\n .use(new ModifiedSinceRule())\n .use(new IfNoneMatchRule())\n .use(new UnmodifiedSinceRule())\n .use(new IfMatchRule());\n\n const cacheResponse = await policy.execute(worker, () =>\n this.getCached(cache, worker.request),\n );\n if (cacheResponse) return cacheResponse;\n\n const response = await next();\n\n worker.ctx.waitUntil(this.setCached(cache, worker.request, response));\n return response;\n }\n\n /**\n * Attempts to retrieve a cached response for the given request.\n *\n * Checks the base cache key first. If the cached response is a `VariantResponse`,\n * it computes the variant-specific key using the `Vary` headers and returns\n * the corresponding cached response. Otherwise, returns the base cached response.\n *\n * Returns `undefined` if no cached response exists or if the cached response\n * fails validation (e.g., rules in `CachePolicy` would prevent it from being used).\n *\n * @param cache - The Cache to query.\n * @param request - The Request for which to retrieve a cached response.\n * @returns A Promise resolving to the cached Response if found and usable, or `undefined`.\n */\n public async getCached(cache: Cache, request: Request): Promise<Response | undefined> {\n const key = this.getCacheKey(request);\n\n const response = await cache.match(key.toString());\n if (!response) return undefined;\n if (!VariantResponse.isVariantResponse(response)) return response;\n\n const vary = VariantResponse.restore(response).vary;\n const varyKey = getVaryKey(request, vary, key);\n return cache.match(varyKey);\n }\n\n /**\n * Stores a response in the cache for the given request, handling `Vary` headers\n * and response variants.\n *\n * The method follows these rules:\n * 1. If the response is not cacheable (per `isCacheable`), it returns immediately.\n * 2. If no cached entry exists:\n * - If the response has no `Vary` headers, the response is cached directly.\n * - If there are `Vary` headers, a `VariantResponse` is created to track\n * which headers affect caching, and both the variant placeholder and the\n * actual response are stored.\n * 3. If a cached entry exists and is a `VariantResponse`:\n * - The `Vary` headers are merged into the variant record.\n * - The variant-specific response is updated in the cache.\n * - TTL is updated to match the most permissive TTL from the origin response.\n * 4. If a cached entry exists but is not a variant and the new response has `Vary` headers:\n * - The cached non-variant is converted into a `VariantResponse`.\n * - Both the new response and the original cached response are stored under appropriate variant keys.\n *\n * @param cache - The Cache where the response should be stored.\n * @param worker - The Worker instance containing the request and execution context.\n * @param response - The Response to cache.\n */\n public async setCached(cache: Cache, request: Request, response: Response): Promise<void> {\n if (!isCacheable(request, response)) return;\n\n const key = this.getCacheKey(request);\n const clone = response.clone();\n const vary = getVaryHeader(clone);\n const cached = await cache.match(key);\n const isCachedVariant = cached && VariantResponse.isVariantResponse(cached);\n\n if (!cached) {\n if (vary.length === 0) {\n await cache.put(key, clone);\n return;\n }\n\n const variantResponse = VariantResponse.new(vary);\n variantResponse.expireAfter(clone);\n await cache.put(key, await variantResponse.response());\n await cache.put(getVaryKey(request, variantResponse.vary, key), clone);\n return;\n }\n\n if (isCachedVariant) {\n const variantResponse = VariantResponse.restore(cached);\n variantResponse.expireAfter(clone);\n if (vary.length > 0) {\n variantResponse.append(vary);\n if (variantResponse.isModified) {\n await cache.put(key, await variantResponse.response());\n }\n }\n await cache.put(getVaryKey(request, variantResponse.vary, key), clone);\n return;\n }\n\n if (vary.length === 0) {\n await cache.put(key, clone);\n return;\n }\n\n // We have an existing cache entry that is non-variant, but the response\n // being processed has a vary header. Create and cache a new variant\n // response that replaces the cached non-variant response. Then save both\n // the new response and the cached response with generated variant keys.\n const variantResponse = VariantResponse.new(vary);\n variantResponse.expireAfter(cached);\n variantResponse.expireAfter(clone);\n await cache.put(key, await variantResponse.response());\n await cache.put(getVaryKey(request, variantResponse.vary, key), clone);\n await cache.put(getVaryKey(request, [], key), cached);\n }\n\n /**\n * Returns the cache key for a request.\n *\n * By default, this is a normalized URL including the path and query string.\n * However, users can provide a custom `getKey` function when creating the\n * `cache` middleware to fully control how the cache keys are generated.\n *\n * For example, a custom function could:\n * - Sort or remove query parameters\n * - Exclude the search/query string entirely\n * - Modify the path or host\n *\n * This allows complete flexibility over cache key generation.\n *\n * @param request The Request object to generate a cache key for.\n * @returns A URL representing the main cache key for this request.\n */\n public getCacheKey(request: Request): URL {\n const key = this.init.getKey(request);\n assertKey(key);\n\n key.hash = \"\";\n return key;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { assertCacheInit } from \"../../guards/cache\";\nimport { CacheInit } from \"../../interfaces/cache\";\nimport { Middleware } from \"../../interfaces/middleware\";\n\nimport { CacheHandler } from \"./handler\";\n\n/**\n * Creates a Vary-aware caching middleware for Workers.\n *\n * This middleware:\n * - Caches `GET` requests **only**.\n * - Respects the `Vary` header of responses, ensuring that requests\n * with different headers (e.g., `Accept-Language`) receive the correct cached response.\n * - Skips caching for non-cacheable responses (e.g., error responses or\n * responses with `Vary: *`).\n *\n * @param init Optional cache configuration object.\n * @param init.name Optional name of the cache to use. If omitted, the default cache is used.\n * @param init.getKey Optional function to compute a custom cache key from a request.\n *\n * @returns A {@link Middleware} instance that can be used in your middleware chain.\n */\nexport function cache(init: Partial<CacheInit> = {}): Middleware {\n assertCacheInit(init);\n\n return new CacheHandler(init);\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CorsInit } from \"../interfaces/cors\";\n\nimport { isBoolean, isNumber, isStringArray } from \"./basic\";\n\n/**\n * Throws if the given value is not a valid CorsInit.\n *\n * Checks only the fields that are present, since CorsInit is Partial<CorsConfig>.\n *\n * @param value - The value to check.\n * @throws TypeError If `value` is not a valid {@link CorsInit}.\n */\nexport function assertCorsInit(value: unknown): asserts value is CorsInit {\n if (value === undefined) return;\n\n if (typeof value !== \"object\" || value === null) {\n throw new TypeError(\"CorsInit must be an object.\");\n }\n\n const obj = value as Record<string, unknown>;\n\n if (obj[\"allowedOrigins\"] !== undefined && !isStringArray(obj[\"allowedOrigins\"])) {\n throw new TypeError(\"CorsInit.allowedOrigins must be a string array.\");\n }\n\n if (obj[\"allowedHeaders\"] !== undefined && !isStringArray(obj[\"allowedHeaders\"])) {\n throw new TypeError(\"CorsInit.allowedHeaders must be a string array.\");\n }\n\n if (obj[\"exposedHeaders\"] !== undefined && !isStringArray(obj[\"exposedHeaders\"])) {\n throw new TypeError(\"CorsInit.exposedHeaders must be a string array.\");\n }\n\n if (obj[\"allowCredentials\"] !== undefined && !isBoolean(obj[\"allowCredentials\"])) {\n throw new TypeError(\"CorsInit.allowCredentials must be a boolean.\");\n }\n\n if (obj[\"maxAge\"] !== undefined && !isNumber(obj[\"maxAge\"])) {\n throw new TypeError(\"CorsInit.maxAge must be a number.\");\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StatusCodes } from \"../../constants\";\nimport { HttpHeader } from \"../../constants/headers\";\nimport { Time } from \"../../constants/time\";\nimport { CorsConfig } from \"../../interfaces/cors\";\n\n/**\n * A wildcard value used in the CORS `Access-Control-Allow-Origin` header\n * to permit requests from **any** origin.\n */\nexport const ALLOW_ALL_ORIGINS = \"*\";\n\n/**\n * Status codes for which `CORS` should be skipped.\n *\n * Skips `CORS` for:\n * - 101 Switching Protocols (WebSocket upgrade)\n * - 100 Continue\n * - 3xx Redirects (`CORS` is applied to the final URL only)\n */\nexport const SKIP_CORS_STATUSES = [\n StatusCodes.SWITCHING_PROTOCOLS,\n StatusCodes.CONTINUE,\n StatusCodes.PROCESSING,\n StatusCodes.EARLY_HINTS,\n StatusCodes.MOVED_PERMANENTLY,\n StatusCodes.MOVED_TEMPORARILY,\n StatusCodes.SEE_OTHER,\n StatusCodes.TEMPORARY_REDIRECT,\n StatusCodes.PERMANENT_REDIRECT,\n];\n\n/**\n * Default configuration for `CORS` middleware.\n */\nexport const defaultCorsConfig: CorsConfig = {\n allowedOrigins: [ALLOW_ALL_ORIGINS],\n allowedHeaders: [HttpHeader.CONTENT_TYPE],\n exposedHeaders: [],\n allowCredentials: false,\n maxAge: 5 * Time.Minute,\n} as const;\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../constants/headers\";\nimport { assertMethods } from \"../../guards/methods\";\nimport { CorsConfig } from \"../../interfaces/cors\";\nimport { Worker } from \"../../interfaces/worker\";\nimport { CopyResponse } from \"../../responses\";\nimport { mergeHeader, setHeader } from \"../../utils/headers\";\n\nimport { ALLOW_ALL_ORIGINS, SKIP_CORS_STATUSES } from \"./constants\";\n\n/**\n * Handles a `CORS` preflight `OPTIONS` request.\n *\n * This function **modifies the provided response** to include the appropriate\n * CORS headers based on the configuration and the request's origin.\n *\n * Steps:\n * 1. Clears any existing CORS headers from the response.\n * 2. Sets `Vary: Origin` if needed for caching purposes.\n * 3. If an `Origin` header is present in the request:\n * - Sets `Access-Control-Allow-Origin`\n * - Sets `Access-Control-Allow-Credentials`\n * - Sets `Access-Control-Allow-Methods`\n * - Sets `Access-Control-Allow-Headers`\n * - Sets `Access-Control-Max-Age`\n *\n * @param response - The original Response object to modify for the preflight.\n * @param worker - The Worker handling the request.\n * @param cors - The CORS configuration to apply.\n * @returns A Response object suitable for responding to the preflight request.\n */\nexport function options(\n response: Response,\n worker: Worker,\n cors: CorsConfig,\n): Promise<Response> {\n const copy = new CopyResponse(response);\n const origin = getOrigin(worker.request);\n\n deleteCorsHeaders(copy.headers);\n setVaryOrigin(copy.headers, cors);\n\n if (origin) {\n setAllowOrigin(copy.headers, cors, origin);\n setAllowCredentials(copy.headers, cors, origin);\n setAllowMethods(copy.headers, worker);\n setAllowHeaders(copy.headers, cors);\n setMaxAge(copy.headers, cors);\n }\n\n return copy.response();\n}\n\n/**\n * Applies CORS headers to an existing response for non-preflight requests.\n *\n * This function **modifies the provided response** to include the appropriate\n * CORS headers based on the configuration and the request's origin.\n *\n * Steps:\n * 1. Clears any existing CORS headers from the response.\n * 2. Sets `Vary: Origin` if needed for caching purposes.\n * 3. If an `Origin` header is present in the request:\n * - Sets `Access-Control-Allow-Origin`\n * - Sets `Access-Control-Allow-Credentials`\n * - Sets `Access-Control-Expose-Headers`\n *\n * @param response - The original Response object to modify.\n * @param worker - The Worker handling the request.\n * @param cors - The CORS configuration to apply.\n * @returns A Response object with CORS headers applied, suitable for returning\n * to the client.\n */\nexport function apply(\n response: Response,\n worker: Worker,\n cors: CorsConfig,\n): Promise<Response> {\n const copy = new CopyResponse(response);\n const origin = getOrigin(worker.request);\n\n deleteCorsHeaders(copy.headers);\n setVaryOrigin(copy.headers, cors);\n\n if (origin) {\n setAllowOrigin(copy.headers, cors, origin);\n setAllowCredentials(copy.headers, cors, origin);\n setExposedHeaders(copy.headers, cors);\n }\n\n return copy.response();\n}\n\n/**\n * Adds `Vary: Origin` when `CORS` is restricted to specific origins.\n * This ensures caches differentiate responses by request origin.\n *\n * Skipped when all origins are allowed.\n *\n * @param headers - The headers object to modify.\n * @param cors - The `CORS` configuration.\n */\nexport function setVaryOrigin(headers: Headers, cors: CorsConfig): void {\n if (!allowAllOrigins(cors)) {\n mergeHeader(headers, HttpHeader.VARY, HttpHeader.ORIGIN);\n }\n}\n\n/**\n * Sets the Access-Control-Allow-Origin header based on the `CORS` config\n * and request origin.\n *\n * @param headers - The headers object to modify.\n * @param cors - The `CORS` configuration.\n * @param origin - The request's origin, or null if not present.\n */\nexport function setAllowOrigin(headers: Headers, cors: CorsConfig, origin: string): void {\n if (allowAllOrigins(cors)) {\n setHeader(headers, HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN, ALLOW_ALL_ORIGINS);\n return;\n }\n\n if (cors.allowedOrigins.includes(origin)) {\n setHeader(headers, HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN, origin);\n }\n}\n\n/**\n * Conditionally sets the `Access-Control-Allow-Credentials` header\n * for a `CORS` response.\n *\n * This header is only set if:\n * 1. `cors.allowCredentials` is true,\n * 2. The configuration does **not** allow any origin (`*`), and\n * 3. The provided `origin` is explicitly listed in `cors.allowedOrigins`.\n *\n * @param headers - The Headers object to modify.\n * @param cors - The `CORS` configuration.\n * @param origin - The origin of the incoming request.\n */\nexport function setAllowCredentials(headers: Headers, cors: CorsConfig, origin: string): void {\n if (!cors.allowCredentials) return;\n if (allowAllOrigins(cors)) return;\n if (!cors.allowedOrigins.includes(origin)) return;\n\n setHeader(headers, HttpHeader.ACCESS_CONTROL_ALLOW_CREDENTIALS, \"true\");\n}\n\n/**\n * Sets the `Access-Control-Allow-Methods` header for a `CORS` response.\n *\n * @param headers - The Headers object to modify.\n * @param worker - The Worker instance used to retrieve allowed methods.\n */\nexport function setAllowMethods(headers: Headers, worker: Worker): void {\n const allowed = worker.getAllowedMethods();\n assertMethods(allowed);\n\n setHeader(headers, HttpHeader.ACCESS_CONTROL_ALLOW_METHODS, allowed);\n}\n\n/**\n * Sets the `Access-Control-Max-Age` header for a `CORS` response.\n *\n * This header indicates how long the results of a preflight request\n * can be cached by the client (in seconds).\n *\n * The value is **clamped to a non-negative integer** to comply with\n * the `CORS` specification:\n * - Decimal values are floored to the nearest integer.\n * - Negative values are treated as `0`.\n *\n * @param headers - The Headers object to modify.\n * @param cors - The `CORS` configuration containing the `maxAge` value in seconds.\n */\nexport function setMaxAge(headers: Headers, cors: CorsConfig): void {\n const maxAge = Math.max(0, Math.floor(cors.maxAge));\n setHeader(headers, HttpHeader.ACCESS_CONTROL_MAX_AGE, String(maxAge));\n}\n\n/**\n * Sets the Access-Control-Allow-Headers header based on the `CORS` configuration.\n *\n * Only the headers explicitly listed in `cors.allowedHeaders` are sent.\n * If the array is empty, no Access-Control-Allow-Headers header is added.\n *\n * @param headers - The Headers object to modify.\n * @param cors - The `CORS` configuration.\n */\nexport function setAllowHeaders(headers: Headers, cors: CorsConfig): void {\n setHeader(headers, HttpHeader.ACCESS_CONTROL_ALLOW_HEADERS, cors.allowedHeaders);\n}\n\n/**\n * Sets the Access-Control-Expose-Headers header for a response.\n *\n * @param headers - The headers object to modify.\n * @param cors - The `CORS` configuration.\n */\nexport function setExposedHeaders(headers: Headers, cors: CorsConfig): void {\n setHeader(headers, HttpHeader.ACCESS_CONTROL_EXPOSE_HEADERS, cors.exposedHeaders);\n}\n\n/**\n * Returns true if the `CORS` config allows all origins ('*').\n *\n * @param cors - The `CORS` configuration.\n */\nexport function allowAllOrigins(cors: CorsConfig): boolean {\n return cors.allowedOrigins.includes(ALLOW_ALL_ORIGINS);\n}\n\n/**\n * Deletes any existing `CORS` headers from the provided headers object.\n *\n * @param headers - The headers object to modify.\n */\nexport function deleteCorsHeaders(headers: Headers): void {\n headers.delete(HttpHeader.ACCESS_CONTROL_MAX_AGE);\n headers.delete(HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN);\n headers.delete(HttpHeader.ACCESS_CONTROL_ALLOW_HEADERS);\n headers.delete(HttpHeader.ACCESS_CONTROL_ALLOW_METHODS);\n headers.delete(HttpHeader.ACCESS_CONTROL_EXPOSE_HEADERS);\n headers.delete(HttpHeader.ACCESS_CONTROL_ALLOW_CREDENTIALS);\n}\n\n/**\n * Determines whether CORS headers should be skipped for a response.\n *\n * Certain responses do not require CORS headers, such as:\n * - Informational or protocol-level responses (1xx)\n * - Redirect responses (3xx)\n * - Responses initiating a protocol upgrade\n *\n * @param response - The Response object to inspect.\n * @returns `true` if CORS headers should be omitted, `false` otherwise.\n */\nexport function skipCors(response: Response): boolean {\n const { status, headers } = response;\n if (SKIP_CORS_STATUSES.includes(status)) return true;\n if (headers.has(HttpHeader.UPGRADE)) return true;\n\n return false;\n}\n\n/**\n * Extracts and normalizes the `Origin` header from a request.\n *\n * Returns the origin (scheme + host + port) as a string if present and valid.\n * Returns `null` if:\n * - The `Origin` header is missing\n * - The `Origin` header is `\"null\"` (opaque origin)\n * - The `Origin` header is malformed\n *\n * @param request - The incoming {@link Request} object.\n * @returns The normalized origin string, or `null` if not present or invalid.\n */\nexport function getOrigin(request: Request): string | null {\n const origin = request.headers.get(HttpHeader.ORIGIN)?.trim();\n if (!origin || origin === \"null\") return null;\n\n try {\n return new URL(origin).origin;\n } catch {\n return null;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OPTIONS } from \"../../constants/methods\";\nimport { CorsConfig, CorsInit } from \"../../interfaces/cors\";\nimport { Middleware } from \"../../interfaces/middleware\";\nimport { Worker } from \"../../interfaces/worker\";\n\nimport { defaultCorsConfig } from \"./constants\";\nimport { apply, options, skipCors } from \"./utils\";\n\n/**\n * CORS Middleware Implementation\n *\n * Handles Cross-Origin Resource Sharing (CORS) for incoming requests.\n *\n * Behavior:\n * - Invokes the downstream middleware for all requests first by calling `next()`.\n * - If the request is an `OPTIONS` request (preflight), transforms the downstream\n * response into a preflight CORS response.\n * - For other HTTP methods, applies CORS headers to the downstream response,\n * unless the response explicitly opts out via `skipCors`.\n *\n * This ensures that all responses comply with the configured CORS rules\n * while still allowing downstream middleware to run for every request.\n *\n * @see {@link cors} for full configuration options and defaults.\n */\nexport class CorsHandler implements Middleware {\n /** The resolved CORS configuration for this middleware instance. */\n private readonly config: CorsConfig;\n\n /**\n * Constructs a new `CorsHandler` instance.\n *\n * Merges the provided partial configuration with the default settings.\n *\n * @param init - Optional partial configuration to override defaults.\n * Any fields not provided will use `defaultCorsConfig`.\n */\n constructor(init?: CorsInit) {\n this.config = { ...defaultCorsConfig, ...init };\n }\n\n /**\n * Applies CORS handling to an incoming request.\n *\n * @param worker - The Worker instance containing the request and context.\n * @param next - Function invoking the next middleware in the chain.\n * @returns A Response object with CORS headers applied.\n * - For `OPTIONS` requests, returns a preflight response based on\n * the downstream response.\n * - For other methods, returns the downstream response with\n * CORS headers applied, unless `skipCors` prevents it.\n */\n public async handle(worker: Worker, next: () => Promise<Response>): Promise<Response> {\n const response = await next();\n\n if (worker.request.method === OPTIONS) {\n return options(response, worker, this.config);\n }\n\n if (skipCors(response)) return response;\n\n return apply(response, worker, this.config);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { assertCorsInit } from \"../../guards/cors\";\nimport { CorsInit } from \"../../interfaces/cors\";\nimport { Middleware } from \"../../interfaces/middleware\";\n\nimport { CorsHandler } from \"./handler\";\n\n/**\n * Returns a `CORS` middleware instance.\n *\n * This middleware automatically handles Cross-Origin Resource Sharing (CORS)\n * for incoming requests, including preflight `OPTIONS` requests, and adds\n * appropriate headers to responses.\n *\n * @param init - Optional configuration for `CORS` behavior. See {@link CorsConfig}.\n * @returns A {@link Middleware} instance that can be used in your middleware chain.\n */\nexport function cors(init?: CorsInit): Middleware {\n assertCorsInit(init);\n return new CorsHandler(init);\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { match } from \"path-to-regexp\";\n\nimport { GET } from \"../../constants/methods\";\nimport { BadRequest, UpgradeRequired } from \"../../errors\";\nimport { Middleware } from \"../../interfaces/middleware\";\nimport { Worker } from \"../../interfaces/worker\";\n\nimport { hasConnectionHeader, hasUpgradeHeader, hasWebSocketVersion } from \"./utils\";\n\n/**\n * Middleware for validating WebSocket upgrade requests.\n *\n * - Only applies to `GET` requests.\n * - Matches requests against a specific path using `path-to-regexp` patterns.\n * - Validates that the request contains required WebSocket headers:\n * - `Connection: Upgrade`\n * - `Upgrade: websocket`\n * - `Sec-WebSocket-Version` matches the expected version\n * - Returns an error response if any validation fails.\n * - Otherwise, passes control to the next middleware or origin handler.\n */\nexport class WebSocketHandler implements Middleware {\n /**\n * Creates a new WebSocketHandler for a specific path.\n *\n * @param path - The request path this handler should intercept for WebSocket upgrades.\n * Supports dynamic segments using `path-to-regexp` syntax.\n */\n constructor(private readonly path: string) {}\n\n /**\n * Handles an incoming request, validating WebSocket upgrade headers.\n *\n * @param worker - The Worker instance containing the request.\n * @param next - Function to invoke the next middleware.\n * @returns A Response object if the request fails WebSocket validation,\n * or the result of `next()` if the request is valid or does not match.\n */\n public handle(worker: Worker, next: () => Promise<Response>): Promise<Response> {\n if (worker.request.method !== GET) {\n return next();\n }\n\n if (!this.isMatch(worker.request)) {\n return next();\n }\n\n const headers = worker.request.headers;\n if (!hasConnectionHeader(headers)) {\n return new BadRequest(\"Missing or invalid 'Connection' header\").response();\n }\n if (!hasUpgradeHeader(headers)) {\n return new BadRequest(\"Missing or invalid 'Upgrade' header\").response();\n }\n if (!hasWebSocketVersion(headers)) {\n return new UpgradeRequired().response();\n }\n\n return next();\n }\n\n /**\n * Checks if the request path matches the configured path for this handler.\n *\n * @param request - The incoming Request object.\n * @returns `true` if the request path matches, `false` otherwise.\n */\n private isMatch(request: Request): boolean {\n return match(this.path)(new URL(request.url).pathname) !== false;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HttpHeader } from \"../../constants/headers\";\nimport { getHeaderValues } from \"../../utils/headers\";\n\nimport { WS_UPGRADE, WS_VERSION, WS_WEBSOCKET } from \"./constants\";\n\n/**\n * Checks if the `Connection` header includes the WebSocket upgrade token.\n *\n * @param headers - The Headers object to inspect.\n * @returns `true` if a WebSocket upgrade is requested via `Connection` header, `false` otherwise.\n */\nexport function hasConnectionHeader(headers: Headers): boolean {\n return getHeaderValues(headers, HttpHeader.CONNECTION).some(\n (value) => value.toLowerCase() === WS_UPGRADE,\n );\n}\n\n/**\n * Checks if the `Upgrade` header requests a WebSocket upgrade.\n *\n * @param headers - The Headers object to inspect.\n * @returns `true` if the `Upgrade` header is set to `websocket`, `false` otherwise.\n */\nexport function hasUpgradeHeader(headers: Headers): boolean {\n return getHeaderValues(headers, HttpHeader.UPGRADE).some(\n (value) => value.toLowerCase() === WS_WEBSOCKET,\n );\n}\n\n/**\n * Determines whether the request includes the correct WebSocket version.\n *\n * @param headers - The Headers object to inspect.\n * @returns `true` if `Sec-WebSocket-Version` matches the expected version, `false` otherwise.\n */\nexport function hasWebSocketVersion(headers: Headers): boolean {\n return headers.get(HttpHeader.SEC_WEBSOCKET_VERSION)?.trim() === WS_VERSION;\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Middleware } from \"../../interfaces/middleware\";\n\nimport { WebSocketHandler } from \"./handler\";\n\n/**\n * Returns a middleware that validates incoming WebSocket upgrade requests.\n *\n * - Only validates the upgrade request; it does **not** perform the actual WebSocket upgrade.\n * - Ensures the request:\n * - Uses the `GET` method.\n * - Matches the specified path, supporting `path-to-regexp` style patterns\n * (e.g., `/chat/:name`).\n * - Contains required WebSocket headers:\n * - `Connection: Upgrade`\n * - `Upgrade: websocket`\n * - `Sec-WebSocket-Version: 13`\n * - Returns an error response if validation fails, otherwise passes control to\n * the next middleware or origin handler.\n *\n * @param path - The URL path to intercept for WebSocket upgrades. Defaults to `/`.\n * Supports dynamic segments using `path-to-regexp` syntax.\n * @returns A {@link Middleware} instance that can be used in your middleware chain.\n *\n * @example\n * ```ts\n * app.use(websocket(\"/chat/:name\"));\n * ```\n */\nexport function websocket(path: string = \"/\"): Middleware {\n return new WebSocketHandler(path);\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isString } from \"./basic\";\n\n/**\n * Checks whether a value is binary data suitable for WebSocket transmission.\n *\n * A value is considered binary if it is an {@link ArrayBuffer} or any\n * {@link ArrayBufferView} (such as a `Uint8Array`, `DataView`, etc.).\n *\n * @param value - The value to check.\n * @returns `true` if `value` is an {@link ArrayBuffer} or {@link ArrayBufferView}, otherwise `false`.\n */\nexport function isBinary(value: unknown): value is ArrayBuffer | ArrayBufferView {\n return value instanceof ArrayBuffer || ArrayBuffer.isView(value);\n}\n\n/**\n * Checks whether a value can be sent over a WebSocket connection.\n *\n * A sendable value is either:\n * - a non-empty string, or\n * - a non-empty {@link ArrayBuffer} / {@link ArrayBufferView}.\n *\n * Empty strings and zero-length binary data are considered non-sendable.\n *\n * @param value - The value to check.\n * @returns `true` if `value` is a non-empty string or binary buffer, otherwise `false`.\n */\nexport function isSendable(value: unknown): value is string | ArrayBuffer | ArrayBufferView {\n if (isString(value)) return value.length > 0;\n if (isBinary(value)) return value.byteLength > 0;\n return false;\n}\n\n/**\n * Asserts that a value is a serializable object suitable for JSON encoding.\n *\n * This function ensures the value is a non-null object and that\n * `JSON.stringify()` succeeds without throwing an error.\n *\n * @param value - The value to validate.\n * @throws {TypeError} If `value` is not an object or cannot be serialized to JSON.\n */\nexport function assertSerializable(value: unknown): asserts value is object {\n if (value === null || typeof value !== \"object\") {\n throw new TypeError(\"WebSocket attachment must be an object\");\n }\n try {\n JSON.stringify(value);\n } catch {\n throw new TypeError(\"WebSocket attachment is not serializable\");\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CustomEventType,\n EventOptions,\n ExtendedEventListener,\n ExtendedEventMap,\n ExtendedEventType,\n} from \"../interfaces/websocket\";\n\n/**\n * Base class for managing WebSocket events, including both standard WebSocket events\n * and internal custom events (`open` and `warn`).\n *\n * This class wraps a native WebSocket instance (`server`) and provides:\n * - Delegation of standard events (`message`, `close`, etc.)\n * - Support for custom events used internally by the library:\n * - `open`: dispatched once when the connection is accepted\n * - `warn`: dispatched whenever a warning occurs\n *\n * Subclasses can call `warn()` or `open()` to trigger custom events.\n */\nexport abstract class WebSocketEvents {\n /** The underlying WebSocket server instance being wrapped. */\n protected readonly server: WebSocket;\n\n /** Internal map of custom event listeners. */\n private customListeners: {\n [K in ExtendedEventType]?: ((ev: ExtendedEventMap[K]) => void)[];\n } = {};\n\n /**\n * @param server - The native WebSocket instance to wrap.\n */\n constructor(server: WebSocket) {\n this.server = server;\n }\n\n /**\n * Adds an event listener for either a standard WebSocket event or a custom event.\n *\n * - Custom events: `open`, `warn`\n * - Standard events: `message`, `close`, `error`, etc.\n *\n * The `close` event is automatically set to `{ once: true }` if no options are provided.\n *\n * @param type - Event type to listen for.\n * @param listener - Callback invoked when the event occurs.\n * @param options - Optional event options (`once`).\n */\n public addEventListener<K extends ExtendedEventType>(\n type: K,\n listener: ExtendedEventListener<K>,\n options?: EventOptions,\n ): void {\n if (WebSocketEvents.isCustomEvent(type)) {\n let arr = this.customListeners[type];\n if (!arr) {\n arr = [];\n this.customListeners[type] = arr;\n }\n arr.push(listener);\n } else {\n const finalOptions = type === \"close\" ? { ...options, once: true } : options;\n this.server.addEventListener(\n type as keyof WebSocketEventMap,\n listener as EventListener,\n finalOptions,\n );\n }\n }\n\n /**\n * Removes a previously registered event listener.\n *\n * Works for both standard WebSocket events and custom events.\n *\n * @param type - Event type to remove.\n * @param listener - Listener function to remove.\n */\n public removeEventListener<K extends ExtendedEventType>(\n type: K,\n listener: ExtendedEventListener<K>,\n ): void {\n if (WebSocketEvents.isCustomEvent(type)) {\n const arr = this.customListeners[type];\n if (arr) {\n const index = arr.indexOf(listener);\n if (index !== -1) arr.splice(index, 1);\n }\n } else {\n this.server.removeEventListener(\n type as keyof WebSocketEventMap,\n listener as EventListener,\n );\n }\n }\n\n /**\n * Dispatches a custom event to all registered listeners.\n *\n * @param type - The custom event type (`open` or `warn`).\n * @param ev - Event object to pass to listeners.\n * @param once - If `true`, all listeners for this event are removed after dispatch.\n */\n private dispatch<K extends CustomEventType>(\n type: K,\n ev: ExtendedEventMap[K],\n once: boolean = false,\n ): void {\n const listeners = this.customListeners[type]?.slice() ?? [];\n if (once) {\n this.customListeners[type] = [];\n }\n for (const listener of listeners) {\n listener(ev);\n }\n }\n\n /**\n * Dispatches a `warn` event with a given message.\n *\n * Intended for internal use to notify listeners of warnings.\n *\n * @param msg - Warning message to emit.\n */\n protected warn(msg: string) {\n this.dispatch(\"warn\", { type: \"warn\", message: msg });\n }\n\n /**\n * Dispatches an `open` event.\n *\n * Intended to signal that the WebSocket has been accepted and is ready.\n * This event is dispatched only once.\n */\n protected open() {\n this.dispatch(\"open\", new Event(\"open\"), true);\n }\n\n /** Internal helper to determine if an event is a custom event. */\n private static isCustomEvent(type: ExtendedEventType): boolean {\n return [\"open\", \"warn\"].includes(type);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** Max close code a user can send */\nexport const WS_MAX_CLOSE_CODE = 4999;\n/** Max number of reason chars a user can send */\nexport const WS_MAX_REASON_CHARS = 123;\n\n/** WebSocket close codes */\nexport const CloseCode = {\n NORMAL: 1000,\n NO_STATUS: 1005,\n ABNORMAL: 1006,\n TLS_HANDSHAKE: 1015,\n} as const;\n\n/** WebSocket RESERVED close codes */\nexport const WS_RESERVED_CODES = new Set<number>([\n CloseCode.NO_STATUS,\n CloseCode.ABNORMAL,\n CloseCode.TLS_HANDSHAKE,\n]);\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isNumber, isString } from \"../guards/basic\";\n\nimport { CloseCode, WS_MAX_CLOSE_CODE, WS_MAX_REASON_CHARS, WS_RESERVED_CODES } from \"./constants\";\n\n/**\n * Normalizes a WebSocket close code to ensure it is safe to send.\n *\n * - Returns `CloseCode.NORMAL` if the code is undefined, out of range, or reserved.\n *\n * @param code - The optional close code to validate.\n * @returns A valid close code to use for WebSocket closure.\n */\nexport function safeCloseCode(code?: number): number {\n if (!isNumber(code)) return CloseCode.NORMAL;\n if (isCodeInRange(code) && !isReservedCode(code)) return code;\n return CloseCode.NORMAL;\n}\n\n/**\n * Determines whether a close code is within the valid WebSocket range.\n *\n * @param code - The code to validate.\n * @returns `true` if the code is within 1000–4999, `false` otherwise.\n */\nexport function isCodeInRange(code: number): boolean {\n return code >= CloseCode.NORMAL && code <= WS_MAX_CLOSE_CODE;\n}\n\n/**\n * Determines whether a close code is reserved by the WebSocket specification.\n *\n * @param code - The code to check.\n * @returns `true` if the code is reserved, `false` otherwise.\n */\nexport function isReservedCode(code: number): boolean {\n return WS_RESERVED_CODES.has(code);\n}\n\n/**\n * Sanitizes a close reason string to comply with WebSocket limits.\n *\n * - Removes non-printable ASCII characters.\n * - Truncates to the maximum allowed length (`WS_MAX_REASON_CHARS`).\n *\n * @param reason - The optional reason string to sanitize.\n * @returns A cleaned reason string or `undefined` if input is invalid.\n */\nexport function safeReason(reason?: string): string | undefined {\n if (!isString(reason)) return;\n return reason.replaceAll(/[^\\x20-\\x7E]/g, \"\").slice(0, WS_MAX_REASON_CHARS);\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { assertSerializable, isSendable } from \"../guards/websocket\";\nimport { WSAttachment } from \"../interfaces/websocket\";\n\nimport { WebSocketEvents } from \"./events\";\nimport { safeCloseCode, safeReason } from \"./utils\";\n\n/**\n * Base class providing core WebSocket functionality and attachment management.\n *\n * Extends `WebSocketEvents` to inherit custom event handling.\n *\n * @template A - Type of the attachment object associated with this WebSocket.\n */\nexport abstract class BaseWebSocket<A extends WSAttachment> extends WebSocketEvents {\n /** Tracks whether the WebSocket has been accepted. */\n protected accepted = false;\n\n /** The underlying WebSocket server instance. */\n protected readonly server: WebSocket;\n\n /**\n * Initializes the base WebSocket wrapper.\n *\n * Registers a listener to handle the underlying WebSocket `close` event.\n *\n * @param server - The underlying WebSocket instance.\n */\n constructor(server: WebSocket) {\n super(server);\n this.server = server;\n this.server.addEventListener(\"close\", this.onclose);\n }\n\n /**\n * Sends a message over the WebSocket if it is open.\n *\n * Performs validation to ensure the WebSocket is in an open state\n * and the data is non-empty and sendable. Emits a warning if not.\n *\n * @param data - The message to send, as a string or binary data.\n */\n public send(data: string | ArrayBuffer | ArrayBufferView): void {\n if (this.isState(WebSocket.CONNECTING, WebSocket.CLOSED)) {\n this.warn(\"Cannot send: WebSocket not open\");\n return;\n }\n if (!isSendable(data)) {\n this.warn(\"Cannot send: empty or invalid data\");\n return;\n }\n\n this.server.send(data);\n }\n\n /**\n * Returns the current attachment associated with this WebSocket.\n *\n * Attachments are stored as serialized objects on the underlying WebSocket.\n *\n * @returns Readonly attachment object of type `A`.\n */\n public get attachment(): Readonly<A> {\n return (this.server.deserializeAttachment() ?? {}) as A;\n }\n\n /**\n * Updates the attachment object for this WebSocket.\n *\n * Merges the provided partial attachment with the current attachment,\n * ensures it is serializable, and stores it on the underlying WebSocket.\n *\n * @param attachment - Partial or full attachment object to store,\n * or `null` to clear the attachment.\n */\n public attach(attachment?: Partial<A> | null): void {\n if (attachment === undefined) return;\n if (attachment === null) {\n this.server.serializeAttachment({});\n } else {\n const current = this.attachment;\n const merged = { ...current, ...attachment };\n assertSerializable(merged);\n this.server.serializeAttachment(merged);\n }\n }\n\n /**\n * Returns the current WebSocket ready state.\n *\n * If the WebSocket has not been accepted, returns `WebSocket.CONNECTING`.\n *\n * @returns The ready state of the WebSocket.\n */\n public get readyState(): number {\n if (!this.accepted) return WebSocket.CONNECTING;\n return this.server.readyState;\n }\n\n /**\n * Checks if the current ready state matches any of the provided states.\n *\n * @param states - One or more WebSocket state constants to check.\n * @returns `true` if the current state matches any provided, `false` otherwise.\n */\n public isState(...states: number[]): boolean {\n return states.includes(this.readyState);\n }\n\n /**\n * Closes the WebSocket safely.\n *\n * Removes the internal `close` listener and closes the underlying WebSocket\n * using validated close code and sanitized reason.\n *\n * @param code - Optional WebSocket close code.\n * @param reason - Optional reason for closing the WebSocket.\n */\n public close(code?: number, reason?: string): void {\n this.server.removeEventListener(\"close\", this.onclose);\n this.server.close(safeCloseCode(code), safeReason(reason));\n }\n\n /** Internal handler for the underlying WebSocket `close` event. */\n private readonly onclose = (event: CloseEvent): void => {\n this.close(event.code, event.reason);\n };\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { WebSocketConnection, WSAttachment } from \"../interfaces/websocket\";\n\nimport { BaseWebSocket } from \"./base\";\n\n/**\n * Internal base for managing a new WebSocket connection.\n *\n * - Creates a WebSocket pair and stores the client side internally.\n * - Provides methods to accept the server WebSocket and retrieve the client.\n * - Ensures the `open` event is dispatched when the connection is accepted.\n * - Tracks whether the connection has already been accepted to prevent multiple acceptances.\n *\n * @template A - Type of the attachment object for this connection.\n */\nexport abstract class NewConnectionBase<A extends WSAttachment>\n extends BaseWebSocket<A>\n implements WebSocketConnection<A>\n{\n /** The client-facing end of the WebSocket pair. */\n private readonly client: WebSocket;\n\n /**\n * Creates a new WebSocket pair and initializes the server side.\n *\n * The client side is stored internally and returned upon acceptance.\n */\n public constructor() {\n const pair = new WebSocketPair();\n const [client, server] = [pair[0], pair[1]];\n super(server);\n this.client = client;\n }\n\n /**\n * Accepts the server WebSocket and returns the client WebSocket.\n *\n * If already accepted, returns the existing client WebSocket.\n * Otherwise, uses a Durable Object state to accept the connection\n * and marks it as ready.\n *\n * @param ctx - DurableObjectState used to accept the WebSocket.\n * @param tags - Optional array of tags to attach to the WebSocket.\n * @returns Readonly client WebSocket ready for use.\n */\n public acceptWebSocket(ctx: DurableObjectState, tags?: string[]): Readonly<WebSocket> {\n if (this.accepted) return this.client;\n ctx.acceptWebSocket(this.server, tags);\n return this.ready();\n }\n\n /**\n * Accepts the server WebSocket and returns the client WebSocket.\n *\n * If already accepted, returns the existing client WebSocket.\n * Otherwise, calls the internal server accept method and marks\n * the connection as ready.\n *\n * @returns Readonly client WebSocket ready for use.\n */\n public accept(): Readonly<WebSocket> {\n if (this.accepted) return this.client;\n this.server.accept();\n return this.ready();\n }\n\n /**\n * Marks the WebSocket connection as ready.\n *\n * Sets the accepted flag, dispatches the `open` event,\n * and returns the client WebSocket.\n *\n * @returns Client WebSocket ready for use.\n */\n private ready(): WebSocket {\n this.accepted = true;\n this.open();\n\n return this.client;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { WebSocketConnection, WSAttachment } from \"../interfaces/websocket\";\n\nimport { BaseWebSocket } from \"./base\";\n\n/**\n * Internal base for a WebSocket connection that has already been restored.\n *\n * - Marks the connection as accepted immediately upon construction.\n * - Overrides acceptance methods to prevent re-accepting an already-active WebSocket.\n * - Throws an error if `accept()` or `acceptWebSocket()` is called.\n *\n * @template A - Type of the attachment object for this connection.\n */\nexport abstract class RestoredConnectionBase<A extends WSAttachment>\n extends BaseWebSocket<A>\n implements WebSocketConnection<A>\n{\n constructor(ws: WebSocket) {\n super(ws);\n this.accepted = true;\n }\n\n /** Not supported for restored connections; throws an error. */\n public accept(): Readonly<WebSocket> {\n throw new Error(\"Do not call accept() on restore\");\n }\n\n /** Not supported for restored connections; throws an error. */\n public acceptWebSocket(): Readonly<WebSocket> {\n throw new Error(\"Do not call acceptWebSocket() on restore\");\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { WebSocketConnection, WSAttachment } from \"../interfaces/websocket\";\n\nimport { NewConnectionBase } from \"./new\";\nimport { RestoredConnectionBase } from \"./restore\";\n\n/**\n * Manages active WebSocket connections in a Cloudflare Workers environment.\n *\n * Provides a simple interface for creating, restoring, and managing\n * WebSocket connections with optional attachments. Users can:\n *\n * - Create new WebSocket connections (`create`) and attach arbitrary data.\n * - Accept connections using the standard WebSocket API (`accept`).\n * - Accept connections using the hibernatable WebSocket API (`acceptWebSocket`),\n * which allows the connection to be put to sleep when inactive.\n * - Restore existing WebSockets into a managed session (`restore`, `restoreAll`),\n * maintaining their hibernation state.\n * - Iterate over active connections or retrieve a connection by its WebSocket instance.\n * - Close a connection cleanly with optional code and reason (`close`).\n *\n * @template A - Type of attachment data stored on each WebSocket connection.\n */\nexport class WebSocketSessions<A extends WSAttachment = WSAttachment> {\n /** @internal Map of active WebSocket to their connection wrapper. */\n private readonly map = new Map<WebSocket, WebSocketConnection<A>>();\n\n /**\n * Create a new WebSocket connection and optionally attach user data.\n *\n * @param attachment - Partial attachment object to initialize the connection with.\n * @returns A `WebSocketConnection` instance ready for accepting and sending messages.\n */\n public create(attachment?: Partial<A>): WebSocketConnection<A> {\n class NewConnection extends NewConnectionBase<A> {\n constructor(private readonly sessions: WebSocketSessions<A>) {\n super();\n }\n\n public override accept(): WebSocket {\n this.addEventListener(\"close\", () => this.sessions.unregister(this.server));\n this.sessions.register(this.server, this);\n return super.accept();\n }\n\n public override acceptWebSocket(ctx: DurableObjectState, tags?: string[]): WebSocket {\n this.sessions.register(this.server, this);\n return super.acceptWebSocket(ctx, tags);\n }\n }\n\n const connection = new NewConnection(this);\n connection.attach(attachment);\n return connection;\n }\n\n /**\n * Wraps an existing WebSocket in a managed connection session.\n *\n * @param ws - An existing WebSocket to restore.\n * @returns A `WebSocketConnection` representing the restored session.\n */\n public restore(ws: WebSocket): WebSocketConnection<A> {\n class RestoredConnection extends RestoredConnectionBase<A> {\n constructor(sessions: WebSocketSessions<A>, restore: WebSocket) {\n super(restore);\n sessions.register(this.server, this);\n }\n }\n return new RestoredConnection(this, ws);\n }\n\n /**\n * Restores multiple WebSockets into managed sessions at once.\n *\n * @param all - Array of WebSocket instances to restore.\n * @returns Array of `WebSocketConnections` restored.\n */\n public restoreAll(all: WebSocket[]): ReadonlyArray<WebSocketConnection<A>> {\n const restored: WebSocketConnection<A>[] = [];\n for (const ws of all) {\n restored.push(this.restore(ws));\n }\n return restored;\n }\n\n /**\n * Retrieves the managed connection for a specific WebSocket, if any.\n *\n * @param ws - WebSocket instance.\n * @returns Corresponding `WebSocketConnection` or `undefined` if not managed.\n */\n public get(ws: WebSocket): WebSocketConnection<A> | undefined {\n return this.map.get(ws);\n }\n\n /**\n * Selects the managed `WebSocketConnection` objects corresponding to the given WebSockets.\n *\n * @param sockets - Array of WebSocket instances to resolve.\n * @returns Array of corresponding `WebSocketConnection` objects.\n */\n public select(sockets: WebSocket[]): WebSocketConnection<A>[] {\n const result: WebSocketConnection<A>[] = [];\n for (const ws of sockets) {\n const con = this.map.get(ws);\n if (con) result.push(con);\n }\n return result;\n }\n\n /**\n * Returns an iterator over all active `WebSocketConnection` objects\n * managed by this session.\n *\n * Useful for iterating over all connections to perform actions such as\n * broadcasting messages.\n *\n * @returns Iterable iterator of all active `WebSocketConnection` objects.\n */\n public values(): IterableIterator<WebSocketConnection<A>> {\n return this.map.values();\n }\n\n /**\n * Returns an iterator over all active raw `WebSocket` instances\n * currently tracked by this session.\n *\n * @returns Iterable iterator of all active `WebSocket` instances.\n */\n public keys(): IterableIterator<WebSocket> {\n return this.map.keys();\n }\n\n /**\n * Closes a managed WebSocket connection with optional code and reason.\n *\n * @param ws - WebSocket to close.\n * @param code - Optional WebSocket close code.\n * @param reason - Optional reason string.\n * @returns `true` if the connection was managed and removed, `false` otherwise.\n */\n public close(ws: WebSocket, code?: number, reason?: string): boolean {\n const con = this.get(ws);\n if (con) con.close(code, reason);\n\n return this.unregister(ws);\n }\n\n /** Iterates over all active WebSocket connections. */\n public *[Symbol.iterator](): IterableIterator<WebSocketConnection<A>> {\n yield* this.values();\n }\n\n /** Registers a connection internally. */\n private register(ws: WebSocket, con: WebSocketConnection<A>): void {\n this.map.set(ws, con);\n }\n\n /** Un-registers a connection internally. */\n private unregister(ws: WebSocket): boolean {\n return this.map.delete(ws);\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Middleware } from \"../interfaces/middleware\";\n\n/**\n * Asserts at runtime that a value implements the `Middleware` interface.\n *\n * @param value - The value to check.\n * @throws TypeError If `handler` does not have a `handle` method.\n */\nexport function assertMiddleware(value: unknown): asserts value is Middleware {\n if (\n value === null ||\n typeof value !== \"object\" ||\n typeof (value as Middleware).handle !== \"function\"\n ) {\n throw new TypeError(\n \"Handler must implement the Middleware interface (have a handle method).\",\n );\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GET, HEAD, Method } from \"../constants/methods\";\nimport { assertMethods, isMethod } from \"../guards/methods\";\nimport { FetchHandler } from \"../interfaces/fetch\";\nimport { Worker, WorkerClass } from \"../interfaces/worker\";\n\n/**\n * Provides the foundational structure for handling requests,\n * environment bindings, and the worker execution context.\n *\n * Features:\n * - Holds the current `Request` object (`request` getter).\n * - Provides access to environment bindings (`env` getter).\n * - Provides access to the worker execution context (`ctx` getter).\n * - Subclasses must implement `fetch()` to process the request.\n */\nexport abstract class BaseWorker implements Worker {\n constructor(\n private readonly _request: Request,\n private readonly _env: Env,\n private readonly _ctx: ExecutionContext,\n ) {}\n\n /** The Request object associated with this worker invocation */\n public get request(): Request {\n return this._request;\n }\n\n /** Environment bindings (e.g., KV, secrets, or other globals) */\n public get env(): Env {\n return this._env;\n }\n\n /** Execution context for background tasks or `waitUntil` */\n public get ctx(): ExecutionContext {\n return this._ctx;\n }\n\n /**\n * Dispatches the incoming request to the appropriate handler and produces a response.\n *\n * Subclasses must implement this method to define how the worker generates a `Response`\n * for the current request. This is the central point where request processing occurs.\n *\n * @returns A Promise that resolves to the `Response` for the request.\n */\n protected abstract dispatch(): Promise<Response>;\n\n /**\n * Determines whether a given HTTP method is allowed for this worker.\n *\n * - GET and HEAD are **always allowed**, in compliance with RFC 9110,\n * even if they are not explicitly listed in `getAllowedMethods()`.\n * - Other methods are allowed only if included in the array returned by\n * `getAllowedMethods()` and are valid HTTP methods.\n *\n * @param method - The HTTP method to check (e.g., \"GET\", \"POST\").\n * @returns `true` if the method is allowed, `false` otherwise.\n */\n public isAllowed(method: string): boolean {\n const methods = this.getAllowedMethods();\n assertMethods(methods);\n\n // GET and HEAD are always allowed per RFC\n if (method === GET || method === HEAD) return true;\n\n return isMethod(method) && methods.includes(method);\n }\n\n public abstract getAllowedMethods(): Method[];\n\n /**\n * Creates a new instance of the current Worker subclass.\n *\n * @param request - The {@link Request} to pass to the new worker instance.\n * @returns A new worker instance of the same subclass as `this`.\n */\n protected create(request: Request): this {\n const ctor = this.constructor as WorkerClass<this>;\n return new ctor(request, this.env, this.ctx);\n }\n\n /**\n * Process the {@link Request} and produce a {@link Response}.\n *\n * @returns A {@link Response} promise for the {@link Request}.\n */\n public abstract fetch(): Promise<Response>;\n\n /**\n * Simplify and standardize {@link Response} creation by extending {@link WorkerResponse}\n * or any of its subclasses and passing to this method.\n *\n * Or directly use any of the built-in classes.\n *\n * ```ts\n * this.response(TextResponse, \"Hello World!\")\n * ```\n *\n * @param ResponseClass The response class to instantiate\n * @param args Additional constructor arguments\n * @returns A Promise resolving to the {@link Response} object\n */\n protected response<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Ctor extends new (...args: any[]) => { response(): Promise<Response> },\n >(ResponseClass: Ctor, ...args: ConstructorParameters<Ctor>): Promise<Response> {\n return new ResponseClass(...args).response();\n }\n\n /**\n * **Ignite** your `Worker` implementation into a Cloudflare handler.\n *\n * @returns A `FetchHandler` that launches a new worker instance for each request.\n *\n * ```ts\n * export default MyWorker.ignite();\n * ```\n */\n public static ignite<W extends Worker>(this: WorkerClass<W>): FetchHandler {\n return {\n fetch: (request: Request, env: Env, ctx: ExecutionContext) =>\n new this(request, env, ctx).fetch(),\n };\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { MethodNotAllowed } from \"../errors\";\nimport { assertMiddleware } from \"../guards/middleware\";\nimport { Middleware } from \"../interfaces/middleware\";\n\nimport { BaseWorker } from \"./base\";\n\n/** Base worker for handling middleware chains. */\nexport abstract class MiddlewareWorker extends BaseWorker {\n /** Middleware handlers registered for this worker. */\n protected readonly middlewares: Middleware[] = [];\n\n /**\n * Hook for subclasses to perform any initialization.\n */\n protected init(): void | Promise<void> {\n return;\n }\n\n /**\n * Add one or more middleware instances to this worker.\n *\n * The middleware will run for every request handled by this worker,\n * in the order they are added.\n *\n * @param middleware - One or more middleware instances to run.\n * @returns `this` to allow chaining multiple `.use()` calls.\n */\n public use(...middleware: Middleware[]): this {\n for (const value of middleware) {\n assertMiddleware(value);\n }\n\n this.middlewares.push(...middleware);\n return this;\n }\n\n /**\n * Executes the middleware chain and dispatches the request if the method\n * is allowed by the worker.\n *\n * @returns The Response produced by the last middleware or `dispatch()`.\n */\n public override async fetch(): Promise<Response> {\n /**\n * Allow subclasses to add middleware to be used for the request.\n */\n await this.init();\n\n const chain = this.middlewares.reduceRight(\n (next, handler) => () => handler.handle(this, next),\n () => {\n if (!this.isAllowed(this.request.method)) {\n return this.response(MethodNotAllowed, this);\n }\n return this.dispatch();\n },\n );\n return chain();\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GET, HEAD, Method, OPTIONS } from \"../constants/methods\";\nimport { LoggedHttpError, MethodNotAllowed, MethodNotImplemented, NotFound } from \"../errors\";\nimport { Head, Options } from \"../responses\";\n\nimport { MiddlewareWorker } from \"./middleware\";\n\n/**\n * Basic worker class providing HTTP method dispatching and error handling.\n */\nexport abstract class BasicWorker extends MiddlewareWorker {\n /**\n * Entry point to handle a fetch request.\n */\n public override async fetch(): Promise<Response> {\n try {\n return await super.fetch();\n } catch (error) {\n return this.response(LoggedHttpError, error);\n }\n }\n\n /**\n * Dispatches the request to the method-specific handler.\n */\n protected override dispatch(): Promise<Response> {\n const method = this.request.method as Method;\n const handler: Record<Method, () => Promise<Response>> = {\n GET: () => this.get(),\n PUT: () => this.put(),\n HEAD: () => this.head(),\n POST: () => this.post(),\n PATCH: () => this.patch(),\n DELETE: () => this.delete(),\n OPTIONS: () => this.options(),\n };\n\n return (handler[method] ?? (() => this.response(MethodNotAllowed, this)))();\n }\n\n /** Override and implement this method for `GET` requests. */\n protected get(): Promise<Response> {\n return this.response(NotFound);\n }\n\n /** Override and implement this method for `PUT` requests. */\n protected put(): Promise<Response> {\n return this.response(MethodNotImplemented, this);\n }\n\n /** Override and implement this method for `POST` requests. */\n protected post(): Promise<Response> {\n return this.response(MethodNotImplemented, this);\n }\n\n /** Override and implement this method for `PATCH` requests. */\n protected patch(): Promise<Response> {\n return this.response(MethodNotImplemented, this);\n }\n\n /** Override and implement this method for `DELETE` requests. */\n protected delete(): Promise<Response> {\n return this.response(MethodNotImplemented, this);\n }\n\n /** Returns the default `OPTIONS` response. */\n protected options(): Promise<Response> {\n return this.response(Options, this);\n }\n\n /**\n * Default handler for `HEAD` requests.\n * Performs a `GET` request and removes the body for `HEAD` semantics.\n *\n * Usually does not need to be overridden as this behavior covers\n * standard `HEAD` requirements.\n */\n protected async head(): Promise<Response> {\n const worker = this.create(\n new Request(this.request.url, { method: GET, headers: this.request.headers }),\n );\n return this.response(Head, await worker.fetch());\n }\n\n /**\n * Returns the HTTP methods allowed by this worker.\n *\n * - GET and HEAD are always allowed per RFC 9110, even if subclasses do not include them here.\n * - OPTIONS is included by default since a default handler is implemented.\n * - Subclasses can override this method to allow additional methods or change the defaults.\n */\n public getAllowedMethods(): Method[] {\n return [GET, HEAD, OPTIONS];\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { match } from \"path-to-regexp\";\n\nimport { Method } from \"./constants/methods\";\nimport { MatchedRoute, PathParams, Route, RouteTable } from \"./interfaces/route\";\n\n/**\n * Container for route definitions and matching logic.\n * Implements Iterable to allow iteration over all routes.\n */\nexport class Routes implements Iterable<Route> {\n /** Internal array of registered routes */\n private readonly routes: Route[] = [];\n\n /**\n * Add routes to the router.\n *\n * Accepts any iterable of [method, path, handler] tuples.\n * This includes arrays, Sets, or generators.\n *\n * @param routes - Iterable of route tuples to add.\n */\n public add(routes: RouteTable): void {\n for (const [method, path, handler] of routes) {\n const matcher = match<PathParams>(path);\n this.routes.push({ method, matcher, handler });\n }\n }\n\n /**\n * Attempt to match a URL against the registered routes.\n *\n * @param method - HTTP method of the request\n * @param url - Full URL string to match against\n * @returns A MatchedRoute object if a route matches, otherwise null\n */\n public match(method: Method, url: string): MatchedRoute | null {\n const pathname = new URL(url).pathname;\n\n for (const route of this) {\n if (route.method !== method) continue;\n\n const found = route.matcher(pathname);\n if (found) return { route, params: found.params };\n }\n\n return null;\n }\n\n /**\n * Iterate over all registered routes.\n */\n public *[Symbol.iterator](): Iterator<Route> {\n yield* this.routes;\n }\n}\n","/*\n * Copyright (C) 2025 Ty Busby\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Method } from \"../constants/methods\";\nimport { NotFound } from \"../errors\";\nimport { RouteHandler, RouteTable } from \"../interfaces/route\";\nimport { WorkerClass } from \"../interfaces/worker\";\nimport { Routes } from \"../routes\";\n\nimport { BaseWorker } from \"./base\";\nimport { BasicWorker } from \"./basic\";\n\n/**\n * Base worker supporting route-based request handling.\n *\n * Subclass `RouteWorker` to define a worker with multiple route handlers.\n *\n * Routes can be registered individually via `route()` or in bulk via `routes()`.\n */\nexport abstract class RouteWorker extends BasicWorker {\n /** Internal table of registered routes. */\n private readonly _routes: Routes = new Routes();\n\n /**\n * Registers a single new route in the worker.\n *\n * When a request matches the specified method and path, the provided handler\n * will be executed. The handler can be either:\n * - A function that receives URL parameters, or\n * - A Worker subclass that will handle the request.\n *\n * @param method - HTTP method for the route (`GET`, `POST`, etc.).\n * @param path - URL path pattern (path-to-regexp, e.g., \"/users/:id\").\n * @param handler - The function or Worker class to run when the route matches.\n * @returns The current worker instance, allowing method chaining.\n */\n protected route(method: Method, path: string, handler: RouteHandler): this {\n this.routes([[method, path, handler]]);\n return this;\n }\n\n /**\n * Registers multiple routes at once in the worker.\n *\n * Each route should be a tuple `[method, path, handler]` where:\n * - `method` - HTTP method for the route (`GET`, `POST`, etc.).\n * - `path` - URL path pattern (path-to-regexp e.g., \"/users/:id\").\n * - `handler` - A function that receives URL parameters or a Worker subclass\n * that will handle the request.\n *\n * @param routes - An iterable of routes to register. Each item is a `[method, path, handler]` tuple.\n * @returns The current worker instance, allowing method chaining.\n */\n protected routes(routes: RouteTable): this {\n this._routes.add(routes);\n return this;\n }\n\n /**\n * Matches the incoming request against registered routes and dispatches it.\n *\n * If a route is found:\n * - If the handler is a Worker class, a new instance is created and its `fetch()` is called.\n * - If the handler is a callback function, it is invoked with the extracted path parameters.\n *\n * If no route matches, the request is passed to the superclass ({@link BasicWorker})\n * `dispatch()` handler.\n *\n * @returns A `Promise<Response>` from the matched handler or parent dispatch.\n */\n protected override async dispatch(): Promise<Response> {\n const found = this._routes.match(this.request.method as Method, this.request.url);\n if (!found) return super.dispatch();\n\n const { handler } = found.route;\n if (RouteWorker.isWorkerClass(handler)) {\n return new handler(this.request, this.env, this.ctx).fetch();\n }\n return handler.call(this, found.params);\n }\n\n /**\n * Runtime type guard to check if a given handler is a Worker class.\n *\n * A Worker class is any class that extends `BaseWorker`.\n *\n * @param handler - The constructor function to test.\n * @returns `true` if `handler` is a subclass of `BaseWorker` at runtime, `false` otherwise.\n */\n private static isWorkerClass(handler: RouteHandler): handler is WorkerClass {\n return Object.prototype.isPrototypeOf.call(BaseWorker.prototype, handler.prototype);\n }\n\n protected override put(): Promise<Response> {\n return this.response(NotFound);\n }\n\n protected override post(): Promise<Response> {\n return this.response(NotFound);\n }\n\n protected override patch(): Promise<Response> {\n return this.response(NotFound);\n }\n\n protected override delete(): Promise<Response> {\n return this.response(NotFound);\n }\n}\n"],"mappings":"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","names":["StatusCodes","CacheLib","CacheControl","Method","GET","PUT","HEAD","POST","PATCH","DELETE","OPTIONS","Time","isStringArray","value","item","isString","isNumber","isBoolean","assertCacheInit","value","name","getKey","assertCacheName","assertGetKey","isString","assertKey","sortSearchParams","request","url","stripSearchParams","CachePolicy","rules","worker","getCached","next","rule","StatusCodes","HttpHeader","FORBIDDEN_304_HEADERS","FORBIDDEN_204_HEADERS","lexCompare","a","b","setHeader","headers","key","value","raw","values","v","lexCompare","mergeHeader","merged","getHeaderValues","filterHeaders","keys","VARY_CACHE_URL","VARY_WILDCARD","isCacheable","request","response","StatusCodes","GET","HttpHeader","getCacheControl","responseCacheControl","ttl","getVaryHeader","headers","CacheControl","getFilteredVary","getHeaderValues","vary","values","h","value","lexCompare","getVaryKey","key","varyPairs","filtered","header","normalizeVaryValue","encoded","base64UrlEncode","name","str","utf8","binary","byte","RANGE_REGEX","ETAG_WEAK_PREFIX","WILDCARD_ETAG","getRange","request","range","HttpHeader","match","start","end","isPreconditionFailed","ifMatch","etag","found","isNotModified","ifNoneMatch","normalizeEtag","array","search","value","toDate","isString","date","getCacheValidators","headers","getHeaderValues","hasCacheValidator","ifModifiedSince","ifUnmodifiedSince","getContentLength","lengthHeader","length","isNumber","CacheControlRule","worker","next","cache","getCacheControl","hasCacheValidator","getReasonPhrase","StatusCodes","METHOD_SET","Method","isMethod","value","isString","isMethodArray","assertMethods","desc","WS_UPGRADE","WS_WEBSOCKET","getReasonPhrase","StatusCodes","UTF8_CHARSET","assertOctetStreamInit","value","obj","size","isNumber","offset","length","withCharset","mediaType","charset","BaseResponse","StatusCodes","withCharset","UTF8_CHARSET","getReasonPhrase","key","value","setHeader","mergeHeader","HttpHeader","filterHeaders","FORBIDDEN_204_HEADERS","FORBIDDEN_304_HEADERS","CacheResponse","cache","CacheControl","WorkerResponse","body","CopyResponse","response","NotModified","SuccessResponse","status","JsonResponse","json","HtmlResponse","charset","TextResponse","OctetStream","_OctetStream","stream","init","assertOctetStreamInit","normalized","size","offset","length","R2ObjectStream","_R2ObjectStream","source","useCache","range","WebSocketUpgrade","client","Head","get","Options","worker","allowed","GET","HEAD","assertMethods","HttpError","JsonResponse","status","details","json","getReasonPhrase","CacheControl","LoggedHttpError","error","StatusCodes","uuid","BadRequest","Unauthorized","Forbidden","NotFound","MethodNotAllowed","worker","methods","assertMethods","HttpHeader","PreconditionFailed","UpgradeRequired","InternalServerError","NotImplemented","MethodNotImplemented","ServiceUnavailable","StatusCodes","ValidationRule","worker","next","response","StatusCodes","header","validators","getCacheValidators","MatchRule","ValidationRule","response","HttpHeader","IfMatchRule","etag","validators","isPreconditionFailed","PreconditionFailed","IfNoneMatchRule","isNotModified","NotModified","MethodRule","worker","next","GET","HEAD","response","Head","LastModifiedRule","ValidationRule","response","toDate","HttpHeader","ModifiedSinceRule","lastModified","validators","modifiedSince","NotModified","UnmodifiedSinceRule","unmodifiedSince","PreconditionFailed","StatusCodes","RangeRule","worker","next","range","getRange","response","StatusCodes","length","getContentLength","SecurityRule","worker","next","headers","HttpHeader","UpgradeRule","worker","next","HttpHeader","VariantResponse","_VariantResponse","WorkerResponse","vary","filtered","getFilteredVary","HttpHeader","source","variant","getHeaderValues","cacheControl","CacheControl","before","response","incoming","getCacheControl","incomingTTL","currentTTL","CacheHandler","init","name","getKey","sortSearchParams","worker","next","cache","cacheResponse","CachePolicy","CacheControlRule","MethodRule","UpgradeRule","SecurityRule","RangeRule","ModifiedSinceRule","IfNoneMatchRule","UnmodifiedSinceRule","IfMatchRule","response","request","key","VariantResponse","vary","varyKey","getVaryKey","isCacheable","clone","getVaryHeader","cached","isCachedVariant","variantResponse","assertKey","cache","init","assertCacheInit","CacheHandler","assertCorsInit","value","obj","isStringArray","isBoolean","isNumber","ALLOW_ALL_ORIGINS","SKIP_CORS_STATUSES","StatusCodes","defaultCorsConfig","HttpHeader","Time","options","response","worker","cors","copy","CopyResponse","origin","getOrigin","deleteCorsHeaders","setVaryOrigin","setAllowOrigin","setAllowCredentials","setAllowMethods","setAllowHeaders","setMaxAge","apply","setExposedHeaders","headers","allowAllOrigins","mergeHeader","HttpHeader","setHeader","ALLOW_ALL_ORIGINS","allowed","assertMethods","maxAge","skipCors","status","SKIP_CORS_STATUSES","request","CorsHandler","init","defaultCorsConfig","worker","next","response","OPTIONS","options","skipCors","apply","cors","init","assertCorsInit","CorsHandler","match","hasConnectionHeader","headers","getHeaderValues","HttpHeader","value","WS_UPGRADE","hasUpgradeHeader","WS_WEBSOCKET","hasWebSocketVersion","WebSocketHandler","path","worker","next","GET","headers","hasConnectionHeader","hasUpgradeHeader","hasWebSocketVersion","UpgradeRequired","BadRequest","request","match","websocket","path","WebSocketHandler","isBinary","value","isSendable","isString","assertSerializable","WebSocketEvents","_WebSocketEvents","server","type","listener","options","arr","finalOptions","index","ev","once","listeners","msg","CloseCode","WS_RESERVED_CODES","safeCloseCode","code","isNumber","isCodeInRange","isReservedCode","CloseCode","WS_RESERVED_CODES","safeReason","reason","isString","BaseWebSocket","WebSocketEvents","server","data","isSendable","attachment","merged","assertSerializable","states","code","reason","safeCloseCode","safeReason","event","NewConnectionBase","BaseWebSocket","pair","client","server","ctx","tags","RestoredConnectionBase","BaseWebSocket","ws","WebSocketSessions","attachment","NewConnection","NewConnectionBase","sessions","ctx","tags","connection","ws","RestoredConnection","RestoredConnectionBase","restore","all","restored","sockets","result","con","code","reason","assertMiddleware","value","BaseWorker","_request","_env","_ctx","method","methods","assertMethods","GET","HEAD","isMethod","request","ctor","ResponseClass","args","env","ctx","MiddlewareWorker","BaseWorker","middleware","value","assertMiddleware","next","handler","MethodNotAllowed","BasicWorker","MiddlewareWorker","error","LoggedHttpError","method","MethodNotAllowed","NotFound","MethodNotImplemented","Options","worker","GET","Head","HEAD","OPTIONS","match","Routes","routes","method","path","handler","matcher","url","pathname","route","found","RouteWorker","_RouteWorker","BasicWorker","Routes","method","path","handler","routes","found","BaseWorker","NotFound"]}