diff --git a/runtime/fastly/builtins/cache-override.cpp b/runtime/fastly/builtins/cache-override.cpp index e73ed55500..ae02029d11 100644 --- a/runtime/fastly/builtins/cache-override.cpp +++ b/runtime/fastly/builtins/cache-override.cpp @@ -120,7 +120,7 @@ host_api::CacheOverrideTag CacheOverride::abi_tag(JSObject *self) { tag.set_stale_while_revalidate(); } - if (!pci(self).isUndefined()) { + if (!pci(self).isUndefined() && pci(self).toBoolean()) { tag.set_pci(); } diff --git a/runtime/fastly/builtins/fetch/fetch.cpp b/runtime/fastly/builtins/fetch/fetch.cpp index 1c703b17a1..4d1d40f992 100644 --- a/runtime/fastly/builtins/fetch/fetch.cpp +++ b/runtime/fastly/builtins/fetch/fetch.cpp @@ -114,9 +114,20 @@ bool must_use_guest_caching(JSContext *cx, HandleObject request) { return false; } +bool http_caching_unsupported = false; bool should_use_guest_caching(JSContext *cx, HandleObject request, bool *should_use_cache) { *should_use_cache = true; + // If we previously found guest caching unsupported then remember that + if (http_caching_unsupported) { + if (must_use_guest_caching(cx, request)) { + JS_ReportErrorASCII(cx, "HTTP caching API is not enabled; please contact support for help"); + return false; + } + *should_use_cache = false; + return true; + } + // Check for pass cache override MOZ_ASSERT(Request::is_instance(request)); JS::RootedObject cache_override( @@ -147,6 +158,7 @@ bool should_use_guest_caching(JSContext *cx, HandleObject request, bool *should_ auto res = request_handle.is_cacheable(); if (auto *err = res.to_err()) { if (host_api::error_is_unsupported(*err)) { + http_caching_unsupported = true; // Guest-side caching is unsupported, so we must use host caching. // If we have hooks we must fail since they require guest caching. if (must_use_guest_caching(cx, request)) { @@ -192,8 +204,11 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue return true; } - if (!Request::apply_cache_override(cx, request)) { - return false; + // cache override only applies to requests with caching + if (!without_caching) { + if (!Request::apply_cache_override(cx, request)) { + return false; + } } if (!Request::apply_auto_decompress_gzip(cx, request)) { @@ -214,7 +229,7 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue { auto request_handle = Request::request_handle(request); auto body = RequestOrResponse::body_handle(request); - auto res = without_caching + auto res = !without_caching ? streaming ? request_handle.send_async_streaming(body, backend_chars) : request_handle.send_async(body, backend_chars) : request_handle.send_async_without_caching(body, backend_chars, streaming); @@ -263,11 +278,6 @@ bool fetch_process_cache_hooks_origin_request(JSContext *cx, JS::HandleObject re return true; } - // TODO: properly integrate - if (!Request::apply_cache_override(cx, request)) { - return false; - } - if (!Request::apply_auto_decompress_gzip(cx, request)) { return false; } @@ -410,7 +420,8 @@ bool background_revalidation_then_handler(JSContext *cx, JS::HandleObject reques JSObject *response_obj = &response.toObject(); MOZ_ASSERT(cache_entry.handle == RequestOrResponse::cache_entry(response_obj).value().handle); auto storage_action = Response::get_and_clear_storage_action(response_obj); - auto cache_write_options = Response::override_cache_options(response_obj); + auto cache_write_options = Response::override_cache_options(response_obj, true); + MOZ_ASSERT(cache_write_options); // Mark interest as complete for this phase. We will create new event interest for the body // streaming promise shortly if needed to simplify return and error paths in this function. ENGINE->decr_event_loop_interest(); @@ -479,7 +490,8 @@ bool stream_back_then_handler(JSContext *cx, JS::HandleObject request, JS::Handl auto storage_action = Response::get_and_clear_storage_action(response_obj); // Override cache write options is set to the final cache write options at the end of the response // process. - auto cache_write_options = Response::override_cache_options(response_obj); + auto cache_write_options = Response::override_cache_options(response_obj, true); + MOZ_ASSERT(cache_write_options); switch (storage_action) { case host_api::HttpStorageAction::Insert: { auto insert_res = cache_entry.transaction_insert_and_stream_back( diff --git a/runtime/fastly/builtins/fetch/fetch.h b/runtime/fastly/builtins/fetch/fetch.h index 82e44238a2..8a8dc7eff6 100644 --- a/runtime/fastly/builtins/fetch/fetch.h +++ b/runtime/fastly/builtins/fetch/fetch.h @@ -3,4 +3,5 @@ namespace fastly::fetch { extern api::Engine *ENGINE; +extern bool http_caching_unsupported; } // namespace fastly::fetch diff --git a/runtime/fastly/builtins/fetch/request-response.cpp b/runtime/fastly/builtins/fetch/request-response.cpp index 132bc70da6..5f5c0d5eda 100644 --- a/runtime/fastly/builtins/fetch/request-response.cpp +++ b/runtime/fastly/builtins/fetch/request-response.cpp @@ -189,15 +189,6 @@ ReadResult read_from_handle_all(JSContext *cx, host_api::HttpBody body) { return {std::move(buf), bytes_read}; } -/** - * Any operation which might change the suggested cache options on a candidate response - * should invalidate the computed suggested cache options. - */ -void invalidate_suggested_cache_options(HandleObject response) { - MOZ_ASSERT(Response::is_instance(response)); - JS::SetReservedSlot(response, static_cast(Response::Slots::SuggestedCacheWriteOptions), - JS::UndefinedValue()); -} /** * Headers behave differently, since we have no event on header changes, and instead we * check the generation integer on the headers object for changes. @@ -215,8 +206,8 @@ bool check_or_bump_suggested_cache_options_gen(JSContext *cx, HandleObject respo return true; } uint32_t headers_gen = Headers::get_generation(headers); - // generation overflow implies always invalidation - if (headers_gen == UINT32_MAX) { + // generation overflow implies always-invalidate + if (headers_gen == INT32_MAX) { return false; } RootedValue last_headers_gen( @@ -411,60 +402,34 @@ bool after_send_then(JSContext *cx, JS::HandleObject response, JS::HandleValue r } } - // TODO: final cache write options computation - // For simplicity, just inject the full final computation into the override options as everything. - - // extra_surrogate_keys_abi: cache_override - // // this is a misnomer: it's actually _keys_ separated by spaces - // .get_surrogate_key() - // // TODO: throw error if surrogate key is invalid? - // .and_then(|k| HeaderValue::to_str(k).ok()) - // .map(str::to_string), - // override_storage_action: None, - // override_pci: cache_override.get_pci(), - // override_stale_while_revalidate: cache_override - // .get_stale_while_revalidate() - // .map(|t| Duration::from_secs(t as u64)), - // override_surrogate_keys_abi: None, - // override_ttl: cache_override - // .get_ttl() - // .map(|t| Duration::from_secs(t as u64)), - // override_vary_rule_abi: None, - - // let storage_action = self - // .override_storage_action - // .take() - // .unwrap_or(self.suggested_storage_action); - - // let suggested = self - // .response - // .suggested_cache_options - // .take() - // .unwrap_or_else(|| self.build_fresh_suggested_cache_options()); - - // let write_options = cache::WriteOptions { - // max_age: self - // .override_ttl - // .map(|ttl| ttl - suggested.initial_age) - // .unwrap_or(suggested.max_age), - // initial_age: suggested.initial_age, - // stale_while_revalidate: self - // .override_stale_while_revalidate - // .unwrap_or(suggested.stale_while_revalidate), - // vary_rule_abi: self - // .override_vary_rule_abi - // .take() - // .unwrap_or(suggested.vary_rule_abi), - // surrogate_keys_abi: self - // .override_surrogate_keys_abi - // .take() - // .unwrap_or(suggested.surrogate_keys_abi), - // sensitive_data: self.override_pci.unwrap_or(suggested.sensitive_data), - // length: self - // .body - // .known_length() - // .filter(|_| self.body_transform.is_no_op()), - // }; + // we set the override cache write options to the final computation, which will then immediately + // be used for the transaction insertion, after which it will be cleared. + auto cache_write_options = Response::override_cache_options(response); + auto suggested_cache_write_options = Response::suggested_cache_options(cx, response); + cache_write_options->initial_age_ns = suggested_cache_write_options->initial_age_ns.value(); + if (!cache_write_options->max_age_ns.has_value()) { + cache_write_options->max_age_ns = suggested_cache_write_options->max_age_ns; + } + if (!cache_write_options->stale_while_revalidate_ns.has_value()) { + cache_write_options->stale_while_revalidate_ns = + suggested_cache_write_options->stale_while_revalidate_ns; + } + if (!cache_write_options->surrogate_keys.has_value()) { + cache_write_options->surrogate_keys = std::move(suggested_cache_write_options->surrogate_keys); + } + if (!cache_write_options->vary_rule.has_value()) { + cache_write_options->vary_rule = std::move(suggested_cache_write_options->vary_rule); + } + if (!cache_write_options->sensitive_data.has_value()) { + cache_write_options->sensitive_data = suggested_cache_write_options->sensitive_data; + } + + delete suggested_cache_write_options; + JS::SetReservedSlot(response, static_cast(Response::Slots::SuggestedCacheWriteOptions), + JS::UndefinedValue()); + + // TODO: set known length from body stream (before transform completed) + // TODO: actually run body transform JS::RootedValue response_val(cx, JS::ObjectValue(*response)); JS::ResolvePromise(cx, response_promise, response_val); @@ -538,15 +503,10 @@ bool RequestOrResponse::process_pending_request(JSContext *cx, suggested_storage_action = host_api::HttpStorageAction::RecordUncacheable; } - // TODO: convert cache override options into override cache write options... - // - surrogate_keys must chain with suggested - // - handling of length (in the no body transform case) host_api::HttpCacheWriteOptions *override_cache_options = reinterpret_cast( cabi_malloc(sizeof(host_api::HttpCacheWriteOptions), 4)); - JS::SetReservedSlot(response, static_cast(Response::Slots::OverrideCacheWriteOptions), - JS::PrivateValue(override_cache_options)); JS::SetReservedSlot(response, static_cast(Response::Slots::StorageAction), JS::Int32Value(static_cast(suggested_storage_action))); JS::SetReservedSlot(response, static_cast(RequestOrResponse::Slots::CacheHandle), @@ -558,8 +518,80 @@ bool RequestOrResponse::process_pending_request(JSContext *cx, RootedObject after_send(cx); if (cache_override) { after_send.set(CacheOverride::afterSend(cache_override)); + + // convert the CacheOverride provided to the request into HttpCacheWriteOptions overrides + // that can still be overridden by the candidate reseponse phase + host_api::HttpCacheWriteOptions *suggested = nullptr; + RootedValue override_ttl(cx, CacheOverride::ttl(cache_override)); + + // overriding TTL is computed in terms of the original age, so we need the suggested calculation + if (!override_ttl.isUndefined()) { + if (!suggested) { + suggested = Response::suggested_cache_options(cx, response); + } + uint64_t ttl_ns = static_cast(override_ttl.toInt32() * 1e9); + uint64_t initial_age_ns = suggested->initial_age_ns.value(); + override_cache_options->max_age_ns = ttl_ns + initial_age_ns; + } + + RootedValue override_swr(cx, CacheOverride::swr(cache_override)); + if (!override_swr.isUndefined()) { + override_cache_options->stale_while_revalidate_ns = + static_cast(override_swr.toInt32() * 1e9); + } + + // overriding surrogate keys composes suggested surrogate keys with the original cache override + // space-split keys, so again, use the suggested computation to do this. + RootedValue override_surrogate_keys(cx, CacheOverride::surrogate_key(cache_override)); + if (!override_surrogate_keys.isUndefined()) { + if (!suggested) { + suggested = Response::suggested_cache_options(cx, response); + } + auto str_val = core::encode(cx, override_surrogate_keys); + if (!str_val) { + return false; + } + + // Get the string data as string_view + std::string_view str(str_val.ptr.get(), str_val.len); + + std::vector keys; + size_t pos = 0; + while (pos < str.length()) { + // Skip any leading spaces + while (pos < str.length() && str[pos] == ' ') { + pos++; + } + + // Find next space + size_t space = str.find(' ', pos); + + // Handle either substring to next space or to end + if (space == std::string_view::npos) { + if (pos < str.length()) { + keys.emplace_back(str.substr(pos)); + } + break; + } else { + if (space > pos) { + keys.emplace_back(str.substr(pos, space - pos)); + } + pos = space + 1; + } + } + + override_cache_options->surrogate_keys = std::move(keys); + } + + RootedValue override_pci(cx, CacheOverride::pci(cache_override)); + if (!override_pci.isUndefined()) { + override_cache_options->sensitive_data = override_pci.toBoolean(); + } } + JS::SetReservedSlot(response, static_cast(Response::Slots::OverrideCacheWriteOptions), + JS::PrivateValue(override_cache_options)); + JS::RootedObject after_send_promise(cx); if (after_send) { JS::RootedValue ret_val(cx); @@ -1779,7 +1811,7 @@ bool Request::apply_auto_decompress_gzip(JSContext *cx, JS::HandleObject self) { } /** - * Apply the CacheOverride to a host-side request handle. + * Apply the CacheOverride to a host-side request handle (for non HTTP cache API). */ bool Request::apply_cache_override(JSContext *cx, JS::HandleObject self) { MOZ_ASSERT(is_instance(self)); @@ -2935,7 +2967,7 @@ bool Response::url_get(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } -// TODO: store version client-side. +// TODO: store version client-side, support version_set for HTTP cache Candidate Response flow. bool Response::version_get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(0) @@ -3409,7 +3441,11 @@ bool Response::cached_get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(0) auto entry = RequestOrResponse::cache_entry(self); if (!entry.has_value()) { - args.rval().setBoolean(false); + if (fetch::http_caching_unsupported) { + args.rval().setUndefined(); + } else { + args.rval().setBoolean(false); + } return true; } args.rval().setBoolean(true); @@ -3439,6 +3475,10 @@ bool Response::ttl_get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(0) auto override_opts = override_cache_options(self); + if (!override_opts) { + args.rval().setUndefined(); + return true; + } auto suggested_opts = suggested_cache_options(cx, self); if (!suggested_opts) { return false; @@ -3458,6 +3498,12 @@ bool Response::ttl_get(JSContext *cx, unsigned argc, JS::Value *vp) { bool Response::age_get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(0) + auto override_opts = override_cache_options(self); + if (!override_opts) { + args.rval().setUndefined(); + return true; + } + auto suggested_opts = suggested_cache_options(cx, self); if (!suggested_opts) { return false; @@ -3472,6 +3518,11 @@ bool Response::swr_get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(0) auto override_opts = override_cache_options(self); + if (!override_opts) { + args.rval().setUndefined(); + return true; + } + auto suggested_opts = suggested_cache_options(cx, self); if (!suggested_opts) { return false; @@ -3489,6 +3540,11 @@ bool Response::vary_get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(0) auto override_opts = override_cache_options(self); + if (!override_opts) { + args.rval().setUndefined(); + return true; + } + auto suggested_opts = suggested_cache_options(cx, self); if (!suggested_opts) { return false; @@ -3563,14 +3619,20 @@ bool Response::surrogateKeys_get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(0) auto override_opts = override_cache_options(self); + if (!override_opts) { + args.rval().setUndefined(); + return true; + } + auto suggested_opts = suggested_cache_options(cx, self); if (!suggested_opts) { return false; } const std::vector &surrogate_keys = - override_opts && !override_opts->surrogate_keys.empty() ? override_opts->surrogate_keys - : suggested_opts->surrogate_keys; + override_opts && !override_opts->surrogate_keys.has_value() + ? override_opts->surrogate_keys.value() + : suggested_opts->surrogate_keys.value(); // Create array with known size JS::RootedObject arr(cx, JS::NewArrayObject(cx, surrogate_keys.size())); @@ -3599,6 +3661,11 @@ bool Response::pci_get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(0) auto override_opts = override_cache_options(self); + if (!override_opts) { + args.rval().setUndefined(); + return true; + } + auto suggested_opts = suggested_cache_options(cx, self); if (!suggested_opts) { return false; @@ -3618,7 +3685,7 @@ bool Response::ttl_set(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(1) auto override_opts = override_cache_options(self); - auto suggested_opts = suggested_cache_options(cx, self); + auto suggested_opts = override_opts ? suggested_cache_options(cx, self) : nullptr; if (!override_opts || !suggested_opts) { JS_ReportErrorLatin1(cx, "Cannot set TTL on non-cached response"); return false; @@ -3632,7 +3699,6 @@ bool Response::ttl_set(JSContext *cx, unsigned argc, JS::Value *vp) { uint64_t ttl_ns = static_cast(seconds * 1e9); uint64_t initial_age_ns = suggested_opts->initial_age_ns.value(); override_opts->max_age_ns = ttl_ns + initial_age_ns; - invalidate_suggested_cache_options(self); args.rval().setUndefined(); return true; @@ -3653,7 +3719,6 @@ bool Response::swr_set(JSContext *cx, unsigned argc, JS::Value *vp) { } override_opts->stale_while_revalidate_ns = static_cast(seconds * 1e9); - invalidate_suggested_cache_options(self); args.rval().setUndefined(); return true; @@ -3710,7 +3775,6 @@ bool Response::vary_set(JSContext *cx, unsigned argc, JS::Value *vp) { } override_opts->vary_rule = std::move(vary_rule); - invalidate_suggested_cache_options(self); args.rval().setUndefined(); return true; @@ -3768,7 +3832,6 @@ bool Response::surrogateKeys_set(JSContext *cx, unsigned argc, JS::Value *vp) { } override_opts->surrogate_keys = std::move(keys); - invalidate_suggested_cache_options(self); args.rval().setUndefined(); return true; @@ -3784,7 +3847,6 @@ bool Response::pci_set(JSContext *cx, unsigned argc, JS::Value *vp) { } override_opts->sensitive_data = JS::ToBoolean(args[0]); - invalidate_suggested_cache_options(self); args.rval().setUndefined(); return true; @@ -4006,12 +4068,17 @@ bool Response::init_class(JSContext *cx, JS::HandleObject global) { (type_error_atom = JS_AtomizeAndPinString(cx, "error")); } -host_api::HttpCacheWriteOptions *Response::override_cache_options(JSObject *response) { +host_api::HttpCacheWriteOptions *Response::override_cache_options(JSObject *response, bool clear) { MOZ_ASSERT(is_instance(response)); - return reinterpret_cast( + auto cache_options = reinterpret_cast( JS::GetReservedSlot(response, static_cast(Response::Slots::OverrideCacheWriteOptions)) .toPrivate()); + if (clear) { + JS::SetReservedSlot(response, static_cast(Response::Slots::OverrideCacheWriteOptions), + JS::PrivateValue(nullptr)); + } + return cache_options; } /** @@ -4037,7 +4104,12 @@ host_api::HttpCacheWriteOptions *Response::suggested_cache_options(JSContext *cx return nullptr; } + // TODO: read from the special surrogate keys header here as part of the suggestion. + auto suggested_cache_options = suggested_cache_options_res.unwrap(); + if (!existing.isUndefined()) { + delete reinterpret_cast(existing.toPrivate()); + } JS::SetReservedSlot(response, static_cast(Response::Slots::SuggestedCacheWriteOptions), JS::PrivateValue(suggested_cache_options)); return suggested_cache_options; @@ -4070,15 +4142,13 @@ void Response::finalize(JS::GCContext *gcx, JSObject *self) { host_api::HttpCacheWriteOptions *cache_write_options = static_cast( suggested_cache_write_options_val.toPrivate()); - free(cache_write_options); + delete cache_write_options; } - auto override_cache_write_options_val = - JS::GetReservedSlot(self, static_cast(Response::Slots::OverrideCacheWriteOptions)); - if (!override_cache_write_options_val.isUndefined()) { - host_api::HttpCacheWriteOptions *cache_write_options = - static_cast( - override_cache_write_options_val.toPrivate()); - free(cache_write_options); + auto override_cache_write_options = reinterpret_cast( + JS::GetReservedSlot(self, static_cast(Response::Slots::OverrideCacheWriteOptions)) + .toPrivate()); + if (override_cache_write_options) { + delete override_cache_write_options; } } diff --git a/runtime/fastly/builtins/fetch/request-response.h b/runtime/fastly/builtins/fetch/request-response.h index d53e6754ff..789384650f 100644 --- a/runtime/fastly/builtins/fetch/request-response.h +++ b/runtime/fastly/builtins/fetch/request-response.h @@ -287,9 +287,11 @@ class Response final : public builtins::FinalizableBuiltinImpl { /** * Override cache options set by the user, and cache override. * - * When unset, implies this response is no longer in its candidate phase, not that it failed. + * When unset, implies this response is not / no longer a candidate response. + * Unsetting is done by the final transaction insert providing clear = true. */ - static host_api::HttpCacheWriteOptions *override_cache_options(JSObject *response); + static host_api::HttpCacheWriteOptions *override_cache_options(JSObject *response, + bool clear = false); /** * Suggested cache options as provided by the host for the request/response pair, and * computed lazily (fallible). diff --git a/runtime/fastly/host-api/host_api.cpp b/runtime/fastly/host-api/host_api.cpp index cb02d05db6..cecd3ac664 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -1852,10 +1852,10 @@ to_fastly_cache_write_options(const HttpCacheWriteOptions *opts) { mask |= FASTLY_HTTP_CACHE_WRITE_OPTIONS_MASK_STALE_WHILE_REVALIDATE_NS; } - if (!opts->surrogate_keys.empty()) { + if (opts->surrogate_keys.has_value()) { // Join surrogate keys with spaces std::string joined_keys; - for (const auto &key : opts->surrogate_keys) { + for (const auto &key : opts->surrogate_keys.value()) { if (!joined_keys.empty()) { joined_keys += ' '; } @@ -1908,10 +1908,10 @@ from_fastly_cache_write_options(const fastly::fastly_http_cache_write_options &f while (pos < keys_str.size()) { size_t space = keys_str.find(' ', pos); if (space == std::string_view::npos) { - opts->surrogate_keys.push_back(std::string(keys_str.substr(pos))); + opts->surrogate_keys->push_back(std::string(keys_str.substr(pos))); break; } - opts->surrogate_keys.push_back(std::string(keys_str.substr(pos, space - pos))); + opts->surrogate_keys->push_back(std::string(keys_str.substr(pos, space - pos))); pos = space + 1; } } diff --git a/runtime/fastly/host-api/host_api_fastly.h b/runtime/fastly/host-api/host_api_fastly.h index f3c712a961..ac53a70b30 100644 --- a/runtime/fastly/host-api/host_api_fastly.h +++ b/runtime/fastly/host-api/host_api_fastly.h @@ -664,7 +664,7 @@ struct HttpCacheWriteOptions final { std::optional stale_while_revalidate_ns; // Optional surrogate keys separated by spaces - std::vector surrogate_keys; + std::optional> surrogate_keys; // Optional length of the response body std::optional length; diff --git a/types/globals.d.ts b/types/globals.d.ts index 75d47ea1f5..69d116d0f2 100644 --- a/types/globals.d.ts +++ b/types/globals.d.ts @@ -1350,12 +1350,14 @@ interface Response extends Body { * Fastly-specific property - Returns whether the `Response` resulted from a cache hit. * For request collapsing, typically one response will show as a miss, and the others * (that awaited that response) will show as hits. + * + * Undefined if the environment does not support the new HTTP Cache hostcalls. */ - readonly cached: boolean; + readonly cached: boolean | undefined; /** * Fastly-specific property - Returns whether the cached `Response` is considered stale. * - * Undefined if the response is not cached. + * Undefined if the response is not cached or the environment does not support the HTTP Cache hostcalls. */ readonly isStale: boolean | undefined; @@ -1365,31 +1367,31 @@ interface Response extends Body { * The TTL determines the duration of "freshness" for the cached response * after it is inserted into the cache. * - * Undefined if the response is not cached. May be modified prior to injection into the cache. + * Undefined if the response is not cached or the environment does not support the HTTP Cache hostcalls. May be modified prior to injection into the cache. */ ttl: number | undefined; /** * Fastly-specific property - The current age of the response, if it is cached. * - * Undefined if the response is not cached. May be modified prior to injection into the cache. + * Undefined if the response is not cached or the environment does not support the HTTP Cache hostcalls. May be modified prior to injection into the cache. */ readonly age: number | undefined; /** * Fastly-specific property - The time for which the response can safely be used despite being considered stale, if it is cached. * - * Undefined if the response is not cached. May be modified prior to injection into the cache. + * Undefined if the response is not cached or the environment does not support the HTTP Cache hostcalls. May be modified prior to injection into the cache. */ swr: number | undefined; /** * Fastly-specific property - The set of request headers for which the response may vary. * - * Undefined if the response is not cached. May be modified prior to injection into the cache. + * Undefined if the response is not cached or the environment does not support the HTTP Cache hostcalls. May be modified prior to injection into the cache. */ vary: Array | undefined; /** * Fastly-specific property - The surrogate keys for the cached response. * - * Undefined if the response is not cached. May be modified prior to injection into the cache. + * Undefined if the response is not cached or the environment does not support the HTTP Cache hostcalls. May be modified prior to injection into the cache. */ surrogateKeys: Array | undefined; /** @@ -1398,9 +1400,9 @@ interface Response extends Body { * See the [Fastly PCI-Compliant Caching and Delivery documentation](https://docs.fastly.com/products/pci-compliant-caching-and-delivery) * for details. * - * Undefined if the response is not cached. May be modified prior to injection into the cache. + * Undefined if the response is not cached or the environment does not support the HTTP Cache hostcalls. May be modified prior to injection into the cache. */ - pci: boolean; + pci: boolean | undefined; } /**