UNPKG

miniflare

Version:

Fun, full-featured, fully-local simulator for Cloudflare Workers

646 lines (638 loc) • 28.4 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from == "object" || typeof from == "function") for (let key of __getOwnPropNames(from)) !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target, mod )); var __decorateClass = (decorators, target, key, kind) => { for (var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target, i = decorators.length - 1, decorator; i >= 0; i--) (decorator = decorators[i]) && (result = (kind ? decorator(target, key, result) : decorator(result)) || result); return kind && result && __defProp(target, key, result), result; }; // ../../node_modules/.pnpm/http-cache-semantics@4.1.1/node_modules/http-cache-semantics/index.js var require_http_cache_semantics = __commonJS({ "../../node_modules/.pnpm/http-cache-semantics@4.1.1/node_modules/http-cache-semantics/index.js"(exports, module) { "use strict"; var statusCodeCacheableByDefault = /* @__PURE__ */ new Set([ 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501 ]), understoodStatuses = /* @__PURE__ */ new Set([ 200, 203, 204, 300, 301, 302, 303, 307, 308, 404, 405, 410, 414, 501 ]), errorStatusCodes = /* @__PURE__ */ new Set([ 500, 502, 503, 504 ]), hopByHopHeaders = { date: !0, // included, because we add Age update Date connection: !0, "keep-alive": !0, "proxy-authenticate": !0, "proxy-authorization": !0, te: !0, trailer: !0, "transfer-encoding": !0, upgrade: !0 }, excludedFromRevalidationUpdate = { // Since the old body is reused, it doesn't make sense to change properties of the body "content-length": !0, "content-encoding": !0, "transfer-encoding": !0, "content-range": !0 }; function toNumberOrZero(s) { let n = parseInt(s, 10); return isFinite(n) ? n : 0; } function isErrorResponse(response) { return response ? errorStatusCodes.has(response.status) : !0; } function parseCacheControl(header) { let cc = {}; if (!header) return cc; let parts = header.trim().split(/,/); for (let part of parts) { let [k, v] = part.split(/=/, 2); cc[k.trim()] = v === void 0 ? !0 : v.trim().replace(/^"|"$/g, ""); } return cc; } function formatCacheControl(cc) { let parts = []; for (let k in cc) { let v = cc[k]; parts.push(v === !0 ? k : k + "=" + v); } if (parts.length) return parts.join(", "); } module.exports = class { constructor(req, res, { shared, cacheHeuristic, immutableMinTimeToLive, ignoreCargoCult, _fromObject } = {}) { if (_fromObject) { this._fromObject(_fromObject); return; } if (!res || !res.headers) throw Error("Response headers missing"); this._assertRequestHasHeaders(req), this._responseTime = this.now(), this._isShared = shared !== !1, this._cacheHeuristic = cacheHeuristic !== void 0 ? cacheHeuristic : 0.1, this._immutableMinTtl = immutableMinTimeToLive !== void 0 ? immutableMinTimeToLive : 24 * 3600 * 1e3, this._status = "status" in res ? res.status : 200, this._resHeaders = res.headers, this._rescc = parseCacheControl(res.headers["cache-control"]), this._method = "method" in req ? req.method : "GET", this._url = req.url, this._host = req.headers.host, this._noAuthorization = !req.headers.authorization, this._reqHeaders = res.headers.vary ? req.headers : null, this._reqcc = parseCacheControl(req.headers["cache-control"]), ignoreCargoCult && "pre-check" in this._rescc && "post-check" in this._rescc && (delete this._rescc["pre-check"], delete this._rescc["post-check"], delete this._rescc["no-cache"], delete this._rescc["no-store"], delete this._rescc["must-revalidate"], this._resHeaders = Object.assign({}, this._resHeaders, { "cache-control": formatCacheControl(this._rescc) }), delete this._resHeaders.expires, delete this._resHeaders.pragma), res.headers["cache-control"] == null && /no-cache/.test(res.headers.pragma) && (this._rescc["no-cache"] = !0); } now() { return Date.now(); } storable() { return !!(!this._reqcc["no-store"] && // A cache MUST NOT store a response to any request, unless: // The request method is understood by the cache and defined as being cacheable, and (this._method === "GET" || this._method === "HEAD" || this._method === "POST" && this._hasExplicitExpiration()) && // the response status code is understood by the cache, and understoodStatuses.has(this._status) && // the "no-store" cache directive does not appear in request or response header fields, and !this._rescc["no-store"] && // the "private" response directive does not appear in the response, if the cache is shared, and (!this._isShared || !this._rescc.private) && // the Authorization header field does not appear in the request, if the cache is shared, (!this._isShared || this._noAuthorization || this._allowsStoringAuthenticated()) && // the response either: // contains an Expires header field, or (this._resHeaders.expires || // contains a max-age response directive, or // contains a s-maxage response directive and the cache is shared, or // contains a public response directive. this._rescc["max-age"] || this._isShared && this._rescc["s-maxage"] || this._rescc.public || // has a status code that is defined as cacheable by default statusCodeCacheableByDefault.has(this._status))); } _hasExplicitExpiration() { return this._isShared && this._rescc["s-maxage"] || this._rescc["max-age"] || this._resHeaders.expires; } _assertRequestHasHeaders(req) { if (!req || !req.headers) throw Error("Request headers missing"); } satisfiesWithoutRevalidation(req) { this._assertRequestHasHeaders(req); let requestCC = parseCacheControl(req.headers["cache-control"]); return requestCC["no-cache"] || /no-cache/.test(req.headers.pragma) || requestCC["max-age"] && this.age() > requestCC["max-age"] || requestCC["min-fresh"] && this.timeToLive() < 1e3 * requestCC["min-fresh"] || this.stale() && !(requestCC["max-stale"] && !this._rescc["must-revalidate"] && (requestCC["max-stale"] === !0 || requestCC["max-stale"] > this.age() - this.maxAge())) ? !1 : this._requestMatches(req, !1); } _requestMatches(req, allowHeadMethod) { return (!this._url || this._url === req.url) && this._host === req.headers.host && // the request method associated with the stored response allows it to be used for the presented request, and (!req.method || this._method === req.method || allowHeadMethod && req.method === "HEAD") && // selecting header fields nominated by the stored response (if any) match those presented, and this._varyMatches(req); } _allowsStoringAuthenticated() { return this._rescc["must-revalidate"] || this._rescc.public || this._rescc["s-maxage"]; } _varyMatches(req) { if (!this._resHeaders.vary) return !0; if (this._resHeaders.vary === "*") return !1; let fields = this._resHeaders.vary.trim().toLowerCase().split(/\s*,\s*/); for (let name of fields) if (req.headers[name] !== this._reqHeaders[name]) return !1; return !0; } _copyWithoutHopByHopHeaders(inHeaders) { let headers = {}; for (let name in inHeaders) hopByHopHeaders[name] || (headers[name] = inHeaders[name]); if (inHeaders.connection) { let tokens = inHeaders.connection.trim().split(/\s*,\s*/); for (let name of tokens) delete headers[name]; } if (headers.warning) { let warnings = headers.warning.split(/,/).filter((warning) => !/^\s*1[0-9][0-9]/.test(warning)); warnings.length ? headers.warning = warnings.join(",").trim() : delete headers.warning; } return headers; } responseHeaders() { let headers = this._copyWithoutHopByHopHeaders(this._resHeaders), age = this.age(); return age > 3600 * 24 && !this._hasExplicitExpiration() && this.maxAge() > 3600 * 24 && (headers.warning = (headers.warning ? `${headers.warning}, ` : "") + '113 - "rfc7234 5.5.4"'), headers.age = `${Math.round(age)}`, headers.date = new Date(this.now()).toUTCString(), headers; } /** * Value of the Date response header or current time if Date was invalid * @return timestamp */ date() { let serverDate = Date.parse(this._resHeaders.date); return isFinite(serverDate) ? serverDate : this._responseTime; } /** * Value of the Age header, in seconds, updated for the current time. * May be fractional. * * @return Number */ age() { let age = this._ageValue(), residentTime = (this.now() - this._responseTime) / 1e3; return age + residentTime; } _ageValue() { return toNumberOrZero(this._resHeaders.age); } /** * Value of applicable max-age (or heuristic equivalent) in seconds. This counts since response's `Date`. * * For an up-to-date value, see `timeToLive()`. * * @return Number */ maxAge() { if (!this.storable() || this._rescc["no-cache"] || this._isShared && this._resHeaders["set-cookie"] && !this._rescc.public && !this._rescc.immutable || this._resHeaders.vary === "*") return 0; if (this._isShared) { if (this._rescc["proxy-revalidate"]) return 0; if (this._rescc["s-maxage"]) return toNumberOrZero(this._rescc["s-maxage"]); } if (this._rescc["max-age"]) return toNumberOrZero(this._rescc["max-age"]); let defaultMinTtl = this._rescc.immutable ? this._immutableMinTtl : 0, serverDate = this.date(); if (this._resHeaders.expires) { let expires = Date.parse(this._resHeaders.expires); return Number.isNaN(expires) || expires < serverDate ? 0 : Math.max(defaultMinTtl, (expires - serverDate) / 1e3); } if (this._resHeaders["last-modified"]) { let lastModified = Date.parse(this._resHeaders["last-modified"]); if (isFinite(lastModified) && serverDate > lastModified) return Math.max( defaultMinTtl, (serverDate - lastModified) / 1e3 * this._cacheHeuristic ); } return defaultMinTtl; } timeToLive() { let age = this.maxAge() - this.age(), staleIfErrorAge = age + toNumberOrZero(this._rescc["stale-if-error"]), staleWhileRevalidateAge = age + toNumberOrZero(this._rescc["stale-while-revalidate"]); return Math.max(0, age, staleIfErrorAge, staleWhileRevalidateAge) * 1e3; } stale() { return this.maxAge() <= this.age(); } _useStaleIfError() { return this.maxAge() + toNumberOrZero(this._rescc["stale-if-error"]) > this.age(); } useStaleWhileRevalidate() { return this.maxAge() + toNumberOrZero(this._rescc["stale-while-revalidate"]) > this.age(); } static fromObject(obj) { return new this(void 0, void 0, { _fromObject: obj }); } _fromObject(obj) { if (this._responseTime) throw Error("Reinitialized"); if (!obj || obj.v !== 1) throw Error("Invalid serialization"); this._responseTime = obj.t, this._isShared = obj.sh, this._cacheHeuristic = obj.ch, this._immutableMinTtl = obj.imm !== void 0 ? obj.imm : 24 * 3600 * 1e3, this._status = obj.st, this._resHeaders = obj.resh, this._rescc = obj.rescc, this._method = obj.m, this._url = obj.u, this._host = obj.h, this._noAuthorization = obj.a, this._reqHeaders = obj.reqh, this._reqcc = obj.reqcc; } toObject() { return { v: 1, t: this._responseTime, sh: this._isShared, ch: this._cacheHeuristic, imm: this._immutableMinTtl, st: this._status, resh: this._resHeaders, rescc: this._rescc, m: this._method, u: this._url, h: this._host, a: this._noAuthorization, reqh: this._reqHeaders, reqcc: this._reqcc }; } /** * Headers for sending to the origin server to revalidate stale response. * Allows server to return 304 to allow reuse of the previous response. * * Hop by hop headers are always stripped. * Revalidation headers may be added or removed, depending on request. */ revalidationHeaders(incomingReq) { this._assertRequestHasHeaders(incomingReq); let headers = this._copyWithoutHopByHopHeaders(incomingReq.headers); if (delete headers["if-range"], !this._requestMatches(incomingReq, !0) || !this.storable()) return delete headers["if-none-match"], delete headers["if-modified-since"], headers; if (this._resHeaders.etag && (headers["if-none-match"] = headers["if-none-match"] ? `${headers["if-none-match"]}, ${this._resHeaders.etag}` : this._resHeaders.etag), headers["accept-ranges"] || headers["if-match"] || headers["if-unmodified-since"] || this._method && this._method != "GET") { if (delete headers["if-modified-since"], headers["if-none-match"]) { let etags = headers["if-none-match"].split(/,/).filter((etag) => !/^\s*W\//.test(etag)); etags.length ? headers["if-none-match"] = etags.join(",").trim() : delete headers["if-none-match"]; } } else this._resHeaders["last-modified"] && !headers["if-modified-since"] && (headers["if-modified-since"] = this._resHeaders["last-modified"]); return headers; } /** * Creates new CachePolicy with information combined from the previews response, * and the new revalidation response. * * Returns {policy, modified} where modified is a boolean indicating * whether the response body has been modified, and old cached body can't be used. * * @return {Object} {policy: CachePolicy, modified: Boolean} */ revalidatedPolicy(request, response) { if (this._assertRequestHasHeaders(request), this._useStaleIfError() && isErrorResponse(response)) return { modified: !1, matches: !1, policy: this }; if (!response || !response.headers) throw Error("Response headers missing"); let matches = !1; if (response.status !== void 0 && response.status != 304 ? matches = !1 : response.headers.etag && !/^\s*W\//.test(response.headers.etag) ? matches = this._resHeaders.etag && this._resHeaders.etag.replace(/^\s*W\//, "") === response.headers.etag : this._resHeaders.etag && response.headers.etag ? matches = this._resHeaders.etag.replace(/^\s*W\//, "") === response.headers.etag.replace(/^\s*W\//, "") : this._resHeaders["last-modified"] ? matches = this._resHeaders["last-modified"] === response.headers["last-modified"] : !this._resHeaders.etag && !this._resHeaders["last-modified"] && !response.headers.etag && !response.headers["last-modified"] && (matches = !0), !matches) return { policy: new this.constructor(request, response), // Client receiving 304 without body, even if it's invalid/mismatched has no option // but to reuse a cached body. We don't have a good way to tell clients to do // error recovery in such case. modified: response.status != 304, matches: !1 }; let headers = {}; for (let k in this._resHeaders) headers[k] = k in response.headers && !excludedFromRevalidationUpdate[k] ? response.headers[k] : this._resHeaders[k]; let newResponse = Object.assign({}, response, { status: this._status, method: this._method, headers }); return { policy: new this.constructor(request, newResponse, { shared: this._isShared, cacheHeuristic: this._cacheHeuristic, immutableMinTimeToLive: this._immutableMinTtl }), modified: !1, matches: !0 }; } }; } }); // src/workers/cache/cache.worker.ts var import_http_cache_semantics = __toESM(require_http_cache_semantics()); import assert from "node:assert"; import { Buffer as Buffer2 } from "node:buffer"; import { DeferredPromise, GET, KeyValueStorage, LogLevel, MiniflareDurableObject, parseRanges, PURGE, PUT } from "miniflare:shared"; // src/workers/kv/constants.ts import { testRegExps } from "miniflare:shared"; var KVLimits = { MIN_CACHE_TTL: 60, MAX_LIST_KEYS: 1e3, MAX_KEY_SIZE: 512, MAX_VALUE_SIZE: 25 * 1024 * 1024, MAX_VALUE_SIZE_TEST: 1024, MAX_METADATA_SIZE: 1024, MAX_BULK_SIZE: 25 * 1024 * 1024 }; var SITES_NO_CACHE_PREFIX = "$__MINIFLARE_SITES__$/"; function isSitesRequest(request) { return new URL(request.url).pathname.startsWith(`/${SITES_NO_CACHE_PREFIX}`); } // src/workers/cache/errors.worker.ts import { HttpError } from "miniflare:shared"; // src/workers/cache/constants.ts var CacheHeaders = { NAMESPACE: "cf-cache-namespace", STATUS: "cf-cache-status" }; // src/workers/cache/errors.worker.ts var CacheError = class extends HttpError { constructor(code, message, headers = []) { super(code, message); this.headers = headers; } toResponse() { return new Response(null, { status: this.code, headers: this.headers }); } context(info) { return this.message += ` (${info})`, this; } }, StorageFailure = class extends CacheError { constructor() { super(413, "Cache storage failed"); } }, PurgeFailure = class extends CacheError { constructor() { super(404, "Couldn't find asset to purge"); } }, CacheMiss = class extends CacheError { constructor() { super( // workerd ignores this, but it's the correct status code 504, "Asset not found in cache", [[CacheHeaders.STATUS, "MISS"]] ); } }, RangeNotSatisfiable = class extends CacheError { constructor(size) { super(416, "Range not satisfiable", [ ["Content-Range", `bytes */${size}`], [CacheHeaders.STATUS, "HIT"] ]); } }; // src/workers/cache/cache.worker.ts function getCacheKey(req) { return req.cf?.cacheKey ? String(req.cf?.cacheKey) : req.url; } function getExpiration(timers, req, res) { let reqHeaders = normaliseHeaders(req.headers); delete reqHeaders["cache-control"]; let resHeaders = normaliseHeaders(res.headers); resHeaders["cache-control"]?.toLowerCase().includes("private=set-cookie") && (resHeaders["cache-control"] = resHeaders["cache-control"]?.toLowerCase().replace(/private=set-cookie;?/i, ""), delete resHeaders["set-cookie"]); let cacheReq = { url: req.url, // If a request gets to the Cache service, it's method will be GET. See README.md for details method: "GET", headers: reqHeaders }, cacheRes = { status: res.status, headers: resHeaders }, originalNow = import_http_cache_semantics.default.prototype.now; import_http_cache_semantics.default.prototype.now = timers.now; try { let policy = new import_http_cache_semantics.default(cacheReq, cacheRes, { shared: !0 }); return { // Check if the request & response is cacheable storable: policy.storable() && !("set-cookie" in resHeaders), expiration: policy.timeToLive(), // Cache Policy Headers is typed as [header: string]: string | string[] | undefined // It's safe to ignore the undefined here, which is what casting to HeadersInit does headers: policy.responseHeaders() }; } finally { import_http_cache_semantics.default.prototype.now = originalNow; } } function normaliseHeaders(headers) { let result = {}; for (let [key, value] of headers) result[key.toLowerCase()] = value; return result; } var etagRegexp = /^(W\/)?"(.+)"$/; function parseETag(value) { return etagRegexp.exec(value.trim())?.[2] ?? void 0; } var utcDateRegexp = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d\d (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d\d\d\d \d\d:\d\d:\d\d GMT$/; function parseUTCDate(value) { return utcDateRegexp.test(value) ? Date.parse(value) : NaN; } function getMatchResponse(reqHeaders, res) { let reqIfNoneMatchHeader = reqHeaders.get("If-None-Match"), resETagHeader = res.headers.get("ETag"); if (reqIfNoneMatchHeader !== null && resETagHeader !== null) { let resETag = parseETag(resETagHeader); if (resETag !== void 0) { if (reqIfNoneMatchHeader.trim() === "*") return new Response(null, { status: 304, headers: res.headers }); for (let reqIfNoneMatch of reqIfNoneMatchHeader.split(",")) if (resETag === parseETag(reqIfNoneMatch)) return new Response(null, { status: 304, headers: res.headers }); } } let reqIfModifiedSinceHeader = reqHeaders.get("If-Modified-Since"), resLastModifiedHeader = res.headers.get("Last-Modified"); if (reqIfModifiedSinceHeader !== null && resLastModifiedHeader !== null) { let reqIfModifiedSince = parseUTCDate(reqIfModifiedSinceHeader); if (parseUTCDate(resLastModifiedHeader) <= reqIfModifiedSince) return new Response(null, { status: 304, headers: res.headers }); } if (res.ranges.length > 0) if (res.status = 206, res.ranges.length > 1) assert(!(res.body instanceof ReadableStream)), res.headers.set("Content-Type", res.body.multipartContentType); else { let { start, end } = res.ranges[0]; res.headers.set( "Content-Range", `bytes ${start}-${end}/${res.totalSize}` ), res.headers.set("Content-Length", `${end - start + 1}`); } return res.body instanceof ReadableStream || (res.body = res.body.body), new Response(res.body, { status: res.status, headers: res.headers }); } var CR = 13, LF = 10, STATUS_REGEXP = /^HTTP\/\d(?:\.\d)? (?<rawStatusCode>\d+) (?<statusText>.*)$/; async function parseHttpResponse(stream) { let buffer = Buffer2.alloc(0), blankLineIndex = -1; for await (let chunk of stream.values({ preventCancel: !0 })) if (buffer = Buffer2.concat([buffer, chunk]), blankLineIndex = buffer.findIndex( (_value, index) => buffer[index] === CR && buffer[index + 1] === LF && buffer[index + 2] === CR && buffer[index + 3] === LF ), blankLineIndex !== -1) break; assert(blankLineIndex !== -1, "Expected to find blank line in HTTP message"); let rawStatusHeaders = buffer.subarray(0, blankLineIndex).toString(), [rawStatus, ...rawHeaders] = rawStatusHeaders.split(`\r `), statusMatch = rawStatus.match(STATUS_REGEXP); assert( statusMatch?.groups != null, `Expected first line ${JSON.stringify(rawStatus)} to be HTTP status line` ); let { rawStatusCode, statusText } = statusMatch.groups, statusCode = parseInt(rawStatusCode), headers = rawHeaders.map((rawHeader) => { let index = rawHeader.indexOf(":"); return [ rawHeader.substring(0, index), rawHeader.substring(index + 1).trim() ]; }), prefix = buffer.subarray( blankLineIndex + 4 /* "\r\n\r\n" */ ), { readable, writable } = new IdentityTransformStream(), writer = writable.getWriter(); return writer.write(prefix).then(() => (writer.releaseLock(), stream.pipeTo(writable))).catch((e) => console.error("Error writing HTTP body:", e)), new Response(readable, { status: statusCode, statusText, headers }); } var SizingStream = class extends TransformStream { size; constructor() { let sizePromise = new DeferredPromise(), size = 0; super({ transform(chunk, controller) { size += chunk.byteLength, controller.enqueue(chunk); }, flush() { sizePromise.resolve(size); } }), this.size = sizePromise; } }, CacheObject = class extends MiniflareDurableObject { #warnedUsage = !1; async #maybeWarnUsage(request) { !this.#warnedUsage && request.cf?.miniflare?.cacheWarnUsage === !0 && (this.#warnedUsage = !0, await this.logWithLevel( LogLevel.WARN, "Cache operations will have no impact if you deploy to a workers.dev subdomain!" )); } #storage; get storage() { return this.#storage ??= new KeyValueStorage(this); } match = async (req) => { await this.#maybeWarnUsage(req); let cacheKey = getCacheKey(req); if (isSitesRequest(req)) throw new CacheMiss(); let resHeaders, resRanges, cached = await this.storage.get(cacheKey, ({ size, headers }) => { resHeaders = new Headers(headers); let contentType = resHeaders.get("Content-Type"), rangeHeader = req.headers.get("Range"); if (rangeHeader !== null && (resRanges = parseRanges(rangeHeader, size), resRanges === void 0)) throw new RangeNotSatisfiable(size); return { ranges: resRanges, contentLength: size, contentType: contentType ?? void 0 }; }); if (cached?.metadata === void 0) throw new CacheMiss(); return assert(resHeaders !== void 0), resHeaders.set("CF-Cache-Status", "HIT"), resRanges ??= [], getMatchResponse(req.headers, { status: cached.metadata.status, headers: resHeaders, ranges: resRanges, body: cached.value, totalSize: cached.metadata.size }); }; put = async (req) => { await this.#maybeWarnUsage(req); let cacheKey = getCacheKey(req); if (isSitesRequest(req)) throw new CacheMiss(); assert(req.body !== null); let res = await parseHttpResponse(req.body), body = res.body; assert(body !== null); let { storable, expiration, headers } = getExpiration( this.timers, req, res ); if (!storable) { try { await body.pipeTo(new WritableStream()); } catch { } throw new StorageFailure(); } let contentLength = parseInt(res.headers.get("Content-Length")), sizePromise; if (Number.isNaN(contentLength)) { let stream = new SizingStream(); body = body.pipeThrough(stream), sizePromise = stream.size; } else sizePromise = Promise.resolve(contentLength); let metadata = sizePromise.then((size) => ({ headers: Object.entries(headers), status: res.status, size })); return await this.storage.put({ key: cacheKey, value: body, expiration: this.timers.now() + expiration, metadata }), new Response(null, { status: 204 }); }; delete = async (req) => { await this.#maybeWarnUsage(req); let cacheKey = getCacheKey(req); if (!await this.storage.delete(cacheKey)) throw new PurgeFailure(); return new Response(null); }; }; __decorateClass([ GET() ], CacheObject.prototype, "match", 2), __decorateClass([ PUT() ], CacheObject.prototype, "put", 2), __decorateClass([ PURGE() ], CacheObject.prototype, "delete", 2); export { CacheObject, parseHttpResponse }; //# sourceMappingURL=cache.worker.js.map