@adonix.org/cloud-spark 2.0.4 → 2.0.5
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/README.md +1 -1
- package/dist/cache.js +2 -2
- package/dist/cache.js.map +1 -1
- package/dist/cors.js +1 -1
- package/dist/cors.js.map +1 -1
- package/dist/index.d.ts +361 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/websocket.js +1 -1
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -2
package/dist/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/guards/basic.ts","../src/guards/cache.ts","../src/middleware/cache/keys.ts","../src/middleware/cache/policy.ts","../src/constants/cache.ts","../src/constants/methods.ts","../src/constants/headers.ts","../src/utils/compare.ts","../src/utils/headers.ts","../src/middleware/cache/utils.ts","../src/middleware/cache/rules/utils.ts","../src/middleware/cache/rules/control.ts","../src/constants/media.ts","../src/utils/media.ts","../src/responses.ts","../src/errors.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"],"names":["isString","value","isNumber","assertCacheInit","name","getKey","assertCacheName","assertGetKey","assertKey","sortSearchParams","request","url","stripSearchParams","CachePolicy","rules","worker","getCached","next","rule","CacheControl","CacheLib","Method","GET","PUT","HEAD","POST","PATCH","DELETE","OPTIONS","HttpHeader","FORBIDDEN_304_HEADERS","FORBIDDEN_204_HEADERS","lexCompare","a","b","setHeader","headers","key","raw","values","v","mergeHeader","merged","getHeaderValues","filterHeaders","keys","VARY_CACHE_URL","VARY_WILDCARD","isCacheable","response","StatusCodes","getCacheControl","responseCacheControl","ttl","getVaryHeader","getFilteredVary","vary","h","getVaryKey","varyPairs","filtered","header","normalizeVaryValue","encoded","base64UrlEncode","str","utf8","binary","byte","RANGE_REGEX","ETAG_WEAK_PREFIX","WILDCARD_ETAG","getRange","range","match","start","end","isPreconditionFailed","ifMatch","etag","found","isNotModified","ifNoneMatch","normalizeEtag","array","search","toDate","date","getCacheValidators","hasCacheValidator","ifModifiedSince","ifUnmodifiedSince","getContentLength","lengthHeader","length","CacheControlRule","cache","UTF8_CHARSET","withCharset","mediaType","charset","BaseResponse","getReasonPhrase","CacheResponse","WorkerResponse","body","NotModified","SuccessResponse","status","JsonResponse","json","Head","get","HttpError","details","PreconditionFailed","ValidationRule","validators","MatchRule","IfMatchRule","IfNoneMatchRule","MethodRule","LastModifiedRule","ModifiedSinceRule","lastModified","modifiedSince","UnmodifiedSinceRule","unmodifiedSince","RangeRule","SecurityRule","UpgradeRule","VariantResponse","_VariantResponse","source","variant","cacheControl","before","incoming","incomingTTL","currentTTL","CacheHandler","init","cacheResponse","varyKey","clone","cached","isCachedVariant","variantResponse"],"mappings":"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","file":"cache.js","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 * 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 \"../middleware/cache/interfaces\";\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 * 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 * 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 *\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 { 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\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\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\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 { getReasonPhrase } from \"http-status-codes/build/es/utils-functions\";\n\nimport { StatusCodes } from \"./constants\";\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\nimport { getReasonPhrase } from \"http-status-codes/build/es/utils-functions\";\n\nimport { StatusCodes } from \"./constants\";\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 { StatusCodes } from \"../../../constants\";\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\n\nimport { StatusCodes } from \"../../../constants\";\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 { Middleware } from \"../../interfaces/middleware\";\nimport { Worker } from \"../../interfaces/worker\";\n\nimport { CacheInit } from \"./interfaces\";\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 { Middleware } from \"../../interfaces/middleware\";\n\nimport { CacheHandler } from \"./handler\";\nimport { CacheInit } from \"./interfaces\";\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/guards/basic.ts","../src/guards/cache.ts","../src/middleware/cache/keys.ts","../src/middleware/cache/policy.ts","../src/constants/cache.ts","../src/constants/methods.ts","../src/constants/status.ts","../src/constants/headers.ts","../src/utils/compare.ts","../src/utils/headers.ts","../src/middleware/cache/utils.ts","../src/middleware/cache/rules/utils.ts","../src/middleware/cache/rules/control.ts","../src/constants/media.ts","../src/utils/media.ts","../src/utils/reasons.ts","../src/responses.ts","../src/errors.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"],"names":["isString","value","isNumber","assertCacheInit","name","getKey","assertCacheName","assertGetKey","assertKey","sortSearchParams","request","url","stripSearchParams","CachePolicy","rules","worker","getCached","next","rule","CacheControl","CacheLib","Method","GET","PUT","HEAD","POST","PATCH","DELETE","OPTIONS","StatusCodes","HttpHeader","FORBIDDEN_304_HEADERS","FORBIDDEN_204_HEADERS","lexCompare","a","b","setHeader","headers","key","raw","values","v","mergeHeader","merged","getHeaderValues","filterHeaders","keys","VARY_CACHE_URL","VARY_WILDCARD","isCacheable","response","getCacheControl","responseCacheControl","ttl","getVaryHeader","getFilteredVary","vary","h","getVaryKey","varyPairs","filtered","header","normalizeVaryValue","encoded","base64UrlEncode","str","utf8","binary","byte","RANGE_REGEX","ETAG_WEAK_PREFIX","WILDCARD_ETAG","getRange","range","match","start","end","isPreconditionFailed","ifMatch","etag","found","isNotModified","ifNoneMatch","normalizeEtag","array","search","toDate","date","getCacheValidators","hasCacheValidator","ifModifiedSince","ifUnmodifiedSince","getContentLength","lengthHeader","length","CacheControlRule","cache","UTF8_CHARSET","withCharset","mediaType","charset","getReasonPhrase","status","c","BaseResponse","CacheResponse","WorkerResponse","body","NotModified","SuccessResponse","JsonResponse","json","Head","get","HttpError","details","PreconditionFailed","ValidationRule","validators","MatchRule","IfMatchRule","IfNoneMatchRule","MethodRule","LastModifiedRule","ModifiedSinceRule","lastModified","modifiedSince","UnmodifiedSinceRule","unmodifiedSince","RangeRule","SecurityRule","UpgradeRule","VariantResponse","_VariantResponse","source","variant","cacheControl","before","incoming","incomingTTL","currentTTL","CacheHandler","init","cacheResponse","varyKey","clone","cached","isCachedVariant","variantResponse"],"mappings":"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","file":"cache.js","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 * 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 \"../middleware/cache/interfaces\";\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 * 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 * https://github.com/prettymuchbryce/http-status-codes\n */\nexport enum StatusCodes {\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.1\n *\n * This interim response indicates that everything so far is OK and that the client should continue with the request or ignore it if it is already finished.\n */\n CONTINUE = 100,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.2\n *\n * This code is sent in response to an Upgrade request header by the client, and indicates the protocol the server is switching too.\n */\n SWITCHING_PROTOCOLS = 101,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.1\n *\n * This code indicates that the server has received and is processing the request, but no response is available yet.\n */\n PROCESSING = 102,\n /**\n * Official Documentation @ https://www.rfc-editor.org/rfc/rfc8297#page-3\n *\n * This code indicates to the client that the server is likely to send a final response with the header fields included in the informational response.\n */\n EARLY_HINTS = 103,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.1\n *\n * The request has succeeded. The meaning of a success varies depending on the HTTP method:\n * GET: The resource has been fetched and is transmitted in the message body.\n * HEAD: The entity headers are in the message body.\n * POST: The resource describing the result of the action is transmitted in the message body.\n * TRACE: The message body contains the request message as received by the server\n */\n OK = 200,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.2\n *\n * The request has succeeded and a new resource has been created as a result of it. This is typically the response sent after a PUT request.\n */\n CREATED = 201,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.3\n *\n * The request has been received but not yet acted upon. It is non-committal, meaning that there is no way in HTTP to later send an asynchronous response indicating the outcome of processing the request. It is intended for cases where another process or server handles the request, or for batch processing.\n */\n ACCEPTED = 202,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.4\n *\n * This response code means returned meta-information set is not exact set as available from the origin server, but collected from a local or a third party copy. Except this condition, 200 OK response should be preferred instead of this response.\n */\n NON_AUTHORITATIVE_INFORMATION = 203,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.5\n *\n * There is no content to send for this request, but the headers may be useful. The user-agent may update its cached headers for this resource with the new ones.\n */\n NO_CONTENT = 204,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.6\n *\n * This response code is sent after accomplishing request to tell user agent reset document view which sent this request.\n */\n RESET_CONTENT = 205,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7233#section-4.1\n *\n * This response code is used because of range header sent by the client to separate download into multiple streams.\n */\n PARTIAL_CONTENT = 206,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.2\n *\n * A Multi-Status response conveys information about multiple resources in situations where multiple status codes might be appropriate.\n */\n MULTI_STATUS = 207,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.1\n *\n * The request has more than one possible responses. User-agent or user should choose one of them. There is no standardized way to choose one of the responses.\n */\n MULTIPLE_CHOICES = 300,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.2\n *\n * This response code means that URI of requested resource has been changed. Probably, new URI would be given in the response.\n */\n MOVED_PERMANENTLY = 301,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.3\n *\n * This response code means that URI of requested resource has been changed temporarily. New changes in the URI might be made in the future. Therefore, this same URI should be used by the client in future requests.\n */\n FOUND = 302,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.4\n *\n * The 302 (Found) status code indicates that the target resource resides temporarily under a different URI. Since the redirection might be altered on occasion, the client ought to continue to use the effective request URI for future requests.\n */\n SEE_OTHER = 303,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.1\n *\n * This is used for caching purposes. It is telling to client that response has not been modified. So, client can continue to use same cached version of response.\n */\n NOT_MODIFIED = 304,\n /**\n * @deprecated\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.6\n *\n * Was defined in a previous version of the HTTP specification to indicate that a requested response must be accessed by a proxy. It has been deprecated due to security concerns regarding in-band configuration of a proxy.\n */\n USE_PROXY = 305,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.7\n *\n * Server sent this response to directing client to get requested resource to another URI with same method that used prior request. This has the same semantic than the 302 Found HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request.\n */\n TEMPORARY_REDIRECT = 307,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7538#section-3\n *\n * This means that the resource is now permanently located at another URI, specified by the Location: HTTP Response header. This has the same semantics as the 301 Moved Permanently HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request.\n */\n PERMANENT_REDIRECT = 308,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.1\n *\n * This response means that server could not understand the request due to invalid syntax.\n */\n BAD_REQUEST = 400,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.1\n *\n * Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response.\n */\n UNAUTHORIZED = 401,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.2\n *\n * This response code is reserved for future use. Initial aim for creating this code was using it for digital payment systems however this is not used currently.\n */\n PAYMENT_REQUIRED = 402,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.3\n *\n * The client does not have access rights to the content, i.e. they are unauthorized, so server is rejecting to give proper response. Unlike 401, the client's identity is known to the server.\n */\n FORBIDDEN = 403,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.4\n *\n * The server can not find requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 to hide the existence of a resource from an unauthorized client. This response code is probably the most famous one due to its frequent occurrence on the web.\n */\n NOT_FOUND = 404,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.5\n *\n * The request method is known by the server but has been disabled and cannot be used. For example, an API may forbid DELETE-ing a resource. The two mandatory methods, GET and HEAD, must never be disabled and should not return this error code.\n */\n METHOD_NOT_ALLOWED = 405,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.6\n *\n * This response is sent when the web server, after performing server-driven content negotiation, doesn't find any content following the criteria given by the user agent.\n */\n NOT_ACCEPTABLE = 406,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.2\n *\n * This is similar to 401 but authentication is needed to be done by a proxy.\n */\n PROXY_AUTHENTICATION_REQUIRED = 407,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.7\n *\n * This response is sent on an idle connection by some servers, even without any previous request by the client. It means that the server would like to shut down this unused connection. This response is used much more since some browsers, like Chrome, Firefox 27+, or IE9, use HTTP pre-connection mechanisms to speed up surfing. Also note that some servers merely shut down the connection without sending this message.\n */\n REQUEST_TIMEOUT = 408,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.8\n *\n * This response is sent when a request conflicts with the current state of the server.\n */\n CONFLICT = 409,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.9\n *\n * This response would be sent when the requested content has been permenently deleted from server, with no forwarding address. Clients are expected to remove their caches and links to the resource. The HTTP specification intends this status code to be used for \"limited-time, promotional services\". APIs should not feel compelled to indicate resources that have been deleted with this status code.\n */\n GONE = 410,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.10\n *\n * The server rejected the request because the Content-Length header field is not defined and the server requires it.\n */\n LENGTH_REQUIRED = 411,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.2\n *\n * The client has indicated preconditions in its headers which the server does not meet.\n */\n PRECONDITION_FAILED = 412,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.11\n *\n * Request entity is larger than limits defined by server; the server might close the connection or return an Retry-After header field.\n */\n REQUEST_TOO_LONG = 413,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.12\n *\n * The URI requested by the client is longer than the server is willing to interpret.\n */\n REQUEST_URI_TOO_LONG = 414,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.13\n *\n * The media format of the requested data is not supported by the server, so the server is rejecting the request.\n */\n UNSUPPORTED_MEDIA_TYPE = 415,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7233#section-4.4\n *\n * The range specified by the Range header field in the request can't be fulfilled; it's possible that the range is outside the size of the target URI's data.\n */\n REQUESTED_RANGE_NOT_SATISFIABLE = 416,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.14\n *\n * This response code means the expectation indicated by the Expect request header field can't be met by the server.\n */\n EXPECTATION_FAILED = 417,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc2324#section-2.3.2\n *\n * Any attempt to brew coffee with a teapot should result in the error code \"418 I'm a teapot\". The resulting entity body MAY be short and stout.\n */\n IM_A_TEAPOT = 418,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6\n *\n * The 507 (Insufficient Storage) status code means the method could not be performed on the resource because the server is unable to store the representation needed to successfully complete the request. This condition is considered to be temporary. If the request which received this status code was the result of a user action, the request MUST NOT be repeated until it is requested by a separate user action.\n */\n INSUFFICIENT_SPACE_ON_RESOURCE = 419,\n /**\n * @deprecated\n * Official Documentation @ https://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt\n *\n * A deprecated response used by the Spring Framework when a method has failed.\n */\n METHOD_FAILURE = 420,\n /**\n * Official Documentation @ https://datatracker.ietf.org/doc/html/rfc7540#section-9.1.2\n *\n * Defined in the specification of HTTP/2 to indicate that a server is not able to produce a response for the combination of scheme and authority that are included in the request URI.\n */\n MISDIRECTED_REQUEST = 421,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.3\n *\n * The request was well-formed but was unable to be followed due to semantic errors.\n */\n UNPROCESSABLE_ENTITY = 422,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.4\n *\n * The resource that is being accessed is locked.\n */\n LOCKED = 423,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.5\n *\n * The request failed due to failure of a previous request.\n */\n FAILED_DEPENDENCY = 424,\n /**\n * Official Documentation @ https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.15\n *\n * The server refuses to perform the request using the current protocol but might be willing to do so after the client upgrades to a different protocol.\n */\n UPGRADE_REQUIRED = 426,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-3\n *\n * The origin server requires the request to be conditional. Intended to prevent the 'lost update' problem, where a client GETs a resource's state, modifies it, and PUTs it back to the server, when meanwhile a third party has modified the state on the server, leading to a conflict.\n */\n PRECONDITION_REQUIRED = 428,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-4\n *\n * The user has sent too many requests in a given amount of time (\"rate limiting\").\n */\n TOO_MANY_REQUESTS = 429,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-5\n *\n * The server is unwilling to process the request because its header fields are too large. The request MAY be resubmitted after reducing the size of the request header fields.\n */\n REQUEST_HEADER_FIELDS_TOO_LARGE = 431,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7725\n *\n * The user-agent requested a resource that cannot legally be provided, such as a web page censored by a government.\n */\n UNAVAILABLE_FOR_LEGAL_REASONS = 451,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.1\n *\n * The server encountered an unexpected condition that prevented it from fulfilling the request.\n */\n INTERNAL_SERVER_ERROR = 500,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.2\n *\n * The request method is not supported by the server and cannot be handled. The only methods that servers are required to support (and therefore that must not return this code) are GET and HEAD.\n */\n NOT_IMPLEMENTED = 501,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.3\n *\n * This error response means that the server, while working as a gateway to get a response needed to handle the request, got an invalid response.\n */\n BAD_GATEWAY = 502,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.4\n *\n * The server is not ready to handle the request. Common causes are a server that is down for maintenance or that is overloaded. Note that together with this response, a user-friendly page explaining the problem should be sent. This responses should be used for temporary conditions and the Retry-After: HTTP header should, if possible, contain the estimated time before the recovery of the service. The webmaster must also take care about the caching-related headers that are sent along with this response, as these temporary condition responses should usually not be cached.\n */\n SERVICE_UNAVAILABLE = 503,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.5\n *\n * This error response is given when the server is acting as a gateway and cannot get a response in time.\n */\n GATEWAY_TIMEOUT = 504,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.6\n *\n * The HTTP version used in the request is not supported by the server.\n */\n HTTP_VERSION_NOT_SUPPORTED = 505,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6\n *\n * The server has an internal configuration error: the chosen variant resource is configured to engage in transparent content negotiation itself, and is therefore not a proper end point in the negotiation process.\n */\n INSUFFICIENT_STORAGE = 507,\n /**\n * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-6\n *\n * The 511 status code indicates that the client needs to authenticate to gain network access.\n */\n NETWORK_AUTHENTICATION_REQUIRED = 511,\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 *\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, GET, StatusCodes } 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\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\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\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 \"../constants\";\n\n/**\n * Returns the standard HTTP reason phrase for a status code.\n * Converts enum names (e.g. NOT_FOUND) into title-cased phrases.\n */\nexport function getReasonPhrase(status: StatusCodes): string {\n return StatusCodes[status]\n .toLowerCase()\n .replaceAll(\"_\", \" \")\n .replaceAll(/\\b\\w/g, (c) => c.toUpperCase());\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 { 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\";\nimport { getReasonPhrase } from \"./utils/reasons\";\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 = \"text/plain; charset=utf-8\";\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\nimport { StatusCodes } from \"./constants\";\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\";\nimport { getReasonPhrase } from \"./utils/reasons\";\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 { StatusCodes } from \"../../../constants\";\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\n\nimport { StatusCodes } from \"../../../constants\";\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 { Middleware } from \"../../interfaces/middleware\";\nimport { Worker } from \"../../interfaces/worker\";\n\nimport { CacheInit } from \"./interfaces\";\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 { Middleware } from \"../../interfaces/middleware\";\n\nimport { CacheHandler } from \"./handler\";\nimport { CacheInit } from \"./interfaces\";\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"]}
|